-
Notifications
You must be signed in to change notification settings - Fork 374
MSI V2 (IMDSv2): Identity‑scoped mTLS certificate cache #5469
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
MSI V2 (IMDSv2): Identity‑scoped mTLS certificate cache #5469
Conversation
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateCache.cs
Outdated
Show resolved
Hide resolved
.ConfigureAwait(false); | ||
|
||
// If using IMDSv2 (mTLS) and the mTLS cert is near expiry, skip the AT cache | ||
if (cachedAccessTokenItem != null && ManagedIdentityClient.s_sourceName == ManagedIdentitySource.ImdsV2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- The condition here does not match the comment. You should only look for a certificate if an mtls pop token is requested
- Let's please avoid peppering the codebase with if (static.sourceName == IMDSv2). First of all, it creates possible race conditions, because you don't know if the source has been evaluated (which is the case here, why would I care what the source is if I have a cached token?). Second, because there should be 1 place (Single Responsability Principle) that decides if MTLS POP is supported or not (e.g. each of the MSI sources should say this)
// If using IMDSv2 (mTLS) and the mTLS cert is near expiry, skip the AT cache | ||
if (cachedAccessTokenItem != null && ManagedIdentityClient.s_sourceName == ManagedIdentitySource.ImdsV2) | ||
{ | ||
string identityKey = ServiceBundle.Config.ClientId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming is confusing. avoid using identity
as it has too many meanings. How about mtlsCertCacheKey
?
} | ||
|
||
// Identity-aware check over the mTLS cert cache | ||
internal static bool IsMtlsCertExpiringSoon(string identityKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THis is not a responsability of the ManagedIdentityClient.
{ | ||
string identityKey = ServiceBundle.Config.ClientId; | ||
|
||
if (ManagedIdentityClient.IsMtlsCertExpiringSoon(identityKey)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about smth like if (cert = cache.GetCertifiacate(key) && ~cert.IsExpiringSoon())
? This will avoid having to expose a new complex API that does 2 things at once.
Alternatively, the cache.GetCertificate() can simply return NULL if the certificate is close to expiry. This is what we do for tokens.
internal static readonly ConcurrentDictionary<string, | ||
(X509Certificate2 Cert, string ClientId, string TenantId, string Endpoint)> s_miCerts = new(); | ||
|
||
internal static void ResetSourceForTest() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name no longer matches intent
{ | ||
s_sourceName = ManagedIdentitySource.None; | ||
s_miCerts.Clear(); | ||
s_timeService = new TimeService(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the time service is that it should be a singleton. A single instance is injected in all classes that deal with time. That instance can either be the real time or a test time. Instead, you are using many instances, so you are not really sure what you test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I agree that a big refactor here could take time, maybe add a task to use TimeService everywhere in MSAL where DateTime.now is used.
private const string WindowsHimdsFilePath = "%Programfiles%\\AzureConnectedMachineAgent\\himds.exe"; | ||
private const string LinuxHimdsFilePath = "/opt/azcmagent/bin/himds"; | ||
internal static ManagedIdentitySource s_sourceName = ManagedIdentitySource.None; | ||
internal static ITimeService s_timeService = new TimeService(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal variables are not prefixed with s_ (see MtlsCertRefreshSkew )
} | ||
|
||
// Test-only helpers | ||
internal static bool IsCertExpiringSoon(X509Certificate2 cert, DateTime nowUtc, TimeSpan skew) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you bother introducing a test service then?
return nowUtc >= (notAfterUtc - skew); | ||
} | ||
|
||
internal static void SetTimeServiceForTest(ITimeService timeService) /* internal - test only usage */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's already a internal property, why do you need an extra setter?
internal static ITimeService s_timeService = new TimeService(); | ||
internal static readonly TimeSpan MtlsCertRefreshSkew = TimeSpan.FromMinutes(5); | ||
|
||
internal static readonly ConcurrentDictionary<string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have you considered moving the cache elsewhere? It seems that this class doesn't even use it?
privateKey); | ||
// 2) Try cached binding; refresh if cert expires within 5 minutes | ||
if (!ManagedIdentityClient.s_miCerts.TryGetValue(key, out var binding) || | ||
ManagedIdentityClient.IsMtlsCertExpiringSoon(key)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another example why IsMtlsCertExpiringSoon
is a bad API. You perform a cache GET twice even though you have your value.
internal static readonly TimeSpan MtlsCertRefreshSkew = TimeSpan.FromMinutes(5); | ||
|
||
internal static readonly ConcurrentDictionary<string, | ||
(X509Certificate2 Cert, string ClientId, string TenantId, string Endpoint)> s_miCerts = new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are caching a lot more than certs, so I am not sure this is a good name.
MsalAccessTokenCacheItem cachedAccessTokenItem = await GetCachedAccessTokenAsync() | ||
.ConfigureAwait(false); | ||
|
||
// If using IMDSv2 (mTLS) and the mTLS cert is near expiry, skip the AT cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this file, you do not take care of requesting a POP token form the cache (for which an IAuthenticationOperation is needed)
This PR adds a process‑wide, identity‑scoped cache for the IMDSv2 mTLS client certificate, wires it into the MSI V2 flow
First iteration: cache the certificate
Next iteration: add tests, plug-in mTLS Authentication Operation to support Pop flows