Skip to content

Conversation

@gladjohn
Copy link
Contributor

@gladjohn gladjohn commented Nov 3, 2025

Fixes - Adds MSI v2 cert to the store (persistent cache)

Changes proposed in this request
The IMDSv2 mTLS PoP flow currently relies only on a process‑local cache. That means new processes (or app restarts) must re‑mint the binding certificate even when a valid one already exists on the machine. This PR layers a best‑effort, cross‑process persisted cache on top of the existing in‑memory cache to reduce reminting, while keeping token acquisition robust (never blocked by persistence).

Testing
unit testing

Performance impact
none

Documentation

  • All relevant documentation is updated.

@gladjohn gladjohn requested a review from a team as a code owner November 3, 2025 02:54
/// - No throws; persistence must not block token acquisition.
/// - Windows-only; FriendlyName semantics are undefined elsewhere.
/// </summary>
internal static class PersistentCertificateStore
Copy link
Member

@bgavrilMS bgavrilMS Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you overusing static? Everything seems static in this design. Does this not need mocking? And a logger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need mocking here, but made few changes.

Comment on lines 198 to 201
PersistentCertificateCacheFactory.Create(requestContext.Logger);

_mtlsCache = new MtlsBindingCache(s_mtlsCertificateCache, persisted);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use dependency injection instead of putting this in the constructor? This should enhance testability.

{
internal interface IMtlsBindingCache
{
Task<Tuple<X509Certificate2, string /*endpoint*/, string /*clientId*/>> GetOrCreateAsync(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create a named tuple?

internal class MtlsBindingInfo
{
    public X509Certificate2 Certificate { get; set; }
    public string Endpoint { get; set; }
    public string ClientId { get; set; }
}

try
{
// Create or open existing
using var m = new Mutex(initiallyOwned: false, name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if an exception occurs before the using block exposes?

}
catch (AbandonedMutexException)
{
entered = true; // prior holder crashed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a log here?


if (!entered)
{
logVerbose?.Invoke($"[PersistentCert] Skip persist (lock busy '{name}').");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we log duration waited?

if (string.IsNullOrEmpty(cn))
return false;

// Unconditional CNs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a difference in string comparison between conditional and unconditional CNs? (Ordinal vs. OrdinalIgnoreCase)

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add additional tests:

  • Edge cases:
    • Invalid or null alias values to ensure proper exception handling or fallback.
    • Very long alias strings to check for buffer overflows or system limitations.
    • Non-Windows platforms
  • Multiple concurrent attempts
  • Exception handling within action: Add tests where the action delegate throws an exception—confirm lock is released and exception bubbles as expected.

{ Assert.Inconclusive("Windows-only"); return; }

var aliasRaw = " my-alias ";
var g = InterprocessLock.GetMutexNameForAlias(aliasRaw, preferGlobal: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use global/local for readability

@@ -0,0 +1,553 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add additional tests:

  • for non-windows behavior, validate intended no-op/error/alternative behavior
  • Add tests to validate behavior when mutex acquisition fails due to errors besides contention (e.g., invalid alias, OS failures), or when the action throws an exception—does the lock get released?
  • Test that repeated calls for the same alias succeed serially—i.e., after the first lock is released, the second can acquire.
  • Test boundary and edge cases for aliases: empty string, extremely long name, special characters, unicode, etc.

@@ -0,0 +1,553 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use private helper methods to reduce repeated code for Windows checks or lock calls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants