-
Notifications
You must be signed in to change notification settings - Fork 379
IMDSv2 mTLS PoP: add best‑effort persisted binding‑cert cache (Windows CurrentUser\My) layered over in‑memory cache; per‑alias mutex; tests #5566
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
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
a49dbaf
peristed cert
gladjohn b811c60
more tests
gladjohn 0ffbe8e
pr comments
gladjohn 4e142b3
address pr comments
gladjohn 34ea66d
Merge branch 'main' into gladjohn/msiv2_persisted_cert
gladjohn 83a04e3
pr comments
gladjohn a1e3a6e
Merge branch 'main' into gladjohn/msiv2_persisted_cert
gladjohn 4a15edf
pr comments
gladjohn 1c9f28e
Merge branch 'gladjohn/msiv2_persisted_cert' of https://github.com/Az…
gladjohn f84aaee
fixed
gladjohn 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
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/IMtlsBindingCache.cs
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 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Identity.Client.Core; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
| { | ||
| internal interface IMtlsBindingCache | ||
gladjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| Task<Tuple<X509Certificate2, string /*endpoint*/, string /*clientId*/>> GetOrCreateAsync( | ||
Robbie-Microsoft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| string cacheKey, | ||
| Func<Task<Tuple<X509Certificate2, string, string>>> factory, | ||
| CancellationToken cancellationToken, | ||
| ILoggerAdapter logger); | ||
| } | ||
| } | ||
31 changes: 31 additions & 0 deletions
31
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/IPersistentCertificateCache.cs
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,31 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Security.Cryptography.X509Certificates; | ||
| using Microsoft.Identity.Client.Core; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
| { | ||
| /// <summary> | ||
| /// Persistence interface for IMDSv2 mTLS binding certificates. | ||
| /// Implementations must be best-effort and non-throwing. | ||
| /// </summary> | ||
| internal interface IPersistentCertificateCache | ||
| { | ||
| /// <summary> | ||
| /// Reads the newest valid (≥24h remaining, has private key) entry for the alias. | ||
| /// </summary> | ||
| bool Read(string alias, out CertificateCacheValue value, ILoggerAdapter logger = null); | ||
gladjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Persists the certificate for the alias (best-effort). Implementations should | ||
| /// tag entries to allow alias scoping and prune expired duplicates conservatively. | ||
| /// </summary> | ||
| void Write(string alias, X509Certificate2 cert, string endpointBase, ILoggerAdapter logger = null); | ||
|
|
||
| /// <summary> | ||
| /// Prunes expired entries for the alias (best-effort). | ||
| /// </summary> | ||
| void Delete(string alias, ILoggerAdapter logger = null); | ||
Robbie-Microsoft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
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
123 changes: 123 additions & 0 deletions
123
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/InterprocessLock.cs
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,123 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Security.Cryptography; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using Microsoft.Identity.Client.PlatformsCommon.Shared; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
| { | ||
| /// <summary> | ||
| /// Executes paramref name="action"/ under a cross-process, per-alias mutex. | ||
| /// We attempt 2 namespaces, in order: | ||
| /// 1) <c>Global\</c> — preferred so we dedupe across all sessions on the machine | ||
| /// (e.g., service + user session). This can be denied by OS policy or missing | ||
| /// SeCreateGlobalPrivilege in some contexts. | ||
| /// 2) <c>Local\</c> — fallback to still dedupe within the current session when | ||
| /// <c>Global\</c> is not permitted. | ||
| /// Using both ensures we never throw (persistence is best-effort) while getting | ||
| /// machine-wide dedupe when allowed and session-local dedupe otherwise. | ||
| /// Notes: | ||
| /// - The mutex name is derived from <c>alias</c> (= cacheKey) via SHA-256 hex (truncated) | ||
| /// to avoid invalid characters / length issues. | ||
| /// - On non-Windows runtimes the Global/Local prefixes are treated as part of the name; | ||
| /// behavior remains correct but dedupe scope is platform-defined. | ||
| /// - Abandoned mutexes are treated as acquired to avoid blocking after a crash. | ||
| /// </summary> | ||
|
|
||
| internal static class InterprocessLock | ||
| { | ||
| // Prefer Global\ for cross-session dedupe; fall back to Local\ | ||
| // if ACLs block Global\ to remain non-throwing. | ||
| public static bool TryWithAliasLock( | ||
| string alias, | ||
| TimeSpan timeout, | ||
| Action action, | ||
| Action<string> logVerbose = null) | ||
| { | ||
| var nameGlobal = GetMutexNameForAlias(alias, preferGlobal: true); | ||
| var nameLocal = GetMutexNameForAlias(alias, preferGlobal: false); | ||
gladjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| foreach (var name in new[] { nameGlobal, nameLocal }) | ||
gladjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| try | ||
| { | ||
| // Create or open existing | ||
| using var m = new Mutex(initiallyOwned: false, name); | ||
Robbie-Microsoft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
gladjohn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Wait to acquire | ||
| bool entered; | ||
|
|
||
| try | ||
| { | ||
| entered = m.WaitOne(timeout); | ||
| } | ||
| catch (AbandonedMutexException) | ||
| { | ||
| entered = true; // prior holder crashed | ||
Robbie-Microsoft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if (!entered) | ||
| { | ||
| logVerbose?.Invoke($"[PersistentCert] Skip persist (lock busy '{name}')."); | ||
Robbie-Microsoft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return false; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| action(); | ||
| } | ||
| finally | ||
| { | ||
| try | ||
| { | ||
| m.ReleaseMutex(); | ||
| } | ||
| catch | ||
| { | ||
| /* best-effort */ | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| catch (UnauthorizedAccessException) | ||
| { | ||
| logVerbose?.Invoke($"[PersistentCert] No access to mutex scope '{name}', trying next."); | ||
| continue; // try Local if Global blocked | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logVerbose?.Invoke($"[PersistentCert] Lock failure '{name}': {ex.Message}"); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| public static string GetMutexNameForAlias(string alias, bool preferGlobal = true) | ||
| { | ||
| string suffix = HashAlias(Canonicalize(alias)); | ||
gladjohn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return (preferGlobal ? @"Global\" : @"Local\") + "MSAL_MI_P_" + suffix; | ||
| } | ||
|
|
||
| private static string Canonicalize(string alias) => (alias ?? string.Empty).Trim().ToUpperInvariant(); | ||
|
|
||
| private static string HashAlias(string s) | ||
| { | ||
| try | ||
| { | ||
| var hex = new CommonCryptographyManager().CreateSha256HashHex(s); | ||
| // Truncate to 32 chars to fit mutex name length limits | ||
| return string.IsNullOrEmpty(hex) ? "0" : (hex.Length > 32 ? hex.Substring(0, 32) : hex); | ||
| } | ||
| catch | ||
| { | ||
| return "0"; | ||
| } | ||
| } | ||
| } | ||
| } | ||
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.