Skip to content

Commit d9c8393

Browse files
authored
[Identity] Disable probe in DAC when MICred is set exclusively (#36047)
### Issues associated with this PR Closes #35971 ### Describe the problem that is addressed by this PR - JS MI credential is currently making a probe request in the `getToken` call. Probe is a feature specific to DAC, so this PR will address that issue and ensures only calling probe when MI is inside of DAC chain. Refer to [MI consistency gist](https://gist.github.com/ahsonkhan/d6c4d3a9780bb058a729b76844923ef1) for more information on this feature - With the addition of `AZURE_TOKEN_CREDENTIALS` variable, we want to make sure standalone MI Cred behavior will be similar, so if `AZURE_TOKEN_CREDENTIALS` is set to only MI Cred, then there should be no probe request. ### What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen? ### Are there test cases added in this PR? _(If not, why?)_ ### Provide a list of related PRs _(if any)_ ### Command used to generate this PR:**_(Applicable only to SDK release request PRs)_ ### Checklists - [ ] Added impacted package name to the issue description - [ ] Does this PR needs any fixes in the SDK Generator?** _(If so, create an Issue in the [Autorest/typescript](https://github.com/Azure/autorest.typescript) repository and link it here)_ - [ ] Added a changelog (if necessary)
1 parent 6ac80a8 commit d9c8393

File tree

7 files changed

+83
-9
lines changed

7 files changed

+83
-9
lines changed

sdk/identity/identity/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44

55
### Features Added
66

7+
- When `AZURE_TOKEN_CREDENTIALS` is set to only `ManagedIdentityCredential`, `DefaultAzureCredential` does not issue a probe request and performs retries with exponential backoff. [#36047](https://github.com/Azure/azure-sdk-for-js/pull/36047)
8+
79
### Breaking Changes
810

911
### Bugs Fixed
1012

13+
- Fixed an issue where `ManagedIdentityCredential` will make an additional probe request in the `getToken` call. [#36047](https://github.com/Azure/azure-sdk-for-js/pull/36047)
14+
1115
### Other Changes
1216

1317
## 4.12.0 (2025-09-09)

sdk/identity/identity/src/credentials/defaultAzureCredential.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ export class DefaultAzureCredential extends ChainedTokenCredential {
147147
credentialFunctions = [createDefaultWorkloadIdentityCredential];
148148
break;
149149
case "managedidentitycredential":
150-
credentialFunctions = [createDefaultManagedIdentityCredential];
150+
// Setting `sendProbeRequest` to false to ensure ManagedIdentityCredential behavior
151+
// is consistent when used standalone in DAC chain or used directly.
152+
credentialFunctions = [
153+
() => createDefaultManagedIdentityCredential({ sendProbeRequest: false }),
154+
];
151155
break;
152156
case "visualstudiocodecredential":
153157
credentialFunctions = [createDefaultVisualStudioCodeCredential];
@@ -181,7 +185,7 @@ export class DefaultAzureCredential extends ChainedTokenCredential {
181185
// 3. Returning a UnavailableDefaultCredential from the factory function if a credential is unavailable for any reason
182186
const credentials: TokenCredential[] = credentialFunctions.map((createCredentialFn) => {
183187
try {
184-
return createCredentialFn(options);
188+
return createCredentialFn(options ?? {});
185189
} catch (err: any) {
186190
logger.warning(
187191
`Skipped ${createCredentialFn.name} because of an error creating the credential: ${err}`,

sdk/identity/identity/src/credentials/defaultAzureCredentialFunctions.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,20 @@ export function createDefaultVisualStudioCodeCredential(
5555
* @internal
5656
*/
5757
export function createDefaultManagedIdentityCredential(
58-
options:
58+
options: (
5959
| DefaultAzureCredentialOptions
6060
| DefaultAzureCredentialResourceIdOptions
61-
| DefaultAzureCredentialClientIdOptions = {},
61+
| DefaultAzureCredentialClientIdOptions
62+
) & { sendProbeRequest?: boolean } = {},
6263
): TokenCredential {
6364
options.retryOptions ??= {
6465
maxRetries: 5,
6566
retryDelayInMs: 800,
6667
};
68+
// ManagedIdentityCredential inside DAC chain should send a probe request by default.
69+
// This is different from standalone ManagedIdentityCredential behavior
70+
// or when AZURE_TOKEN_CREDENTIALS is set to only ManagedIdentityCredential.
71+
options.sendProbeRequest ??= true;
6772
const managedIdentityClientId =
6873
(options as DefaultAzureCredentialClientIdOptions)?.managedIdentityClientId ??
6974
process.env.AZURE_CLIENT_ID;

sdk/identity/identity/src/credentials/managedIdentityCredential/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { tokenExchangeMsi } from "./tokenExchangeMsi.js";
1818
import { mapScopesToResource, serviceFabricErrorMessage } from "./utils.js";
1919
import type { MsalToken, ValidMsalToken } from "../../msal/types.js";
2020
import type {
21+
InternalManagedIdentityCredentialOptions,
2122
ManagedIdentityCredentialClientIdOptions,
2223
ManagedIdentityCredentialObjectIdOptions,
2324
ManagedIdentityCredentialResourceIdOptions,
@@ -45,6 +46,7 @@ export class ManagedIdentityCredential implements TokenCredential {
4546
intervalIncrement: 2,
4647
};
4748
private isAvailableIdentityClient: IdentityClient;
49+
private sendProbeRequest: boolean;
4850

4951
/**
5052
* Creates an instance of ManagedIdentityCredential with the client ID of a
@@ -94,7 +96,8 @@ export class ManagedIdentityCredential implements TokenCredential {
9496
}
9597
this.resourceId = (_options as ManagedIdentityCredentialResourceIdOptions)?.resourceId;
9698
this.objectId = (_options as ManagedIdentityCredentialObjectIdOptions)?.objectId;
97-
99+
this.sendProbeRequest =
100+
(_options as InternalManagedIdentityCredentialOptions)?.sendProbeRequest ?? false;
98101
// For JavaScript users.
99102
const providedIds = [
100103
{ key: "clientId", value: this.clientId },
@@ -247,7 +250,7 @@ export class ManagedIdentityCredential implements TokenCredential {
247250
}
248251

249252
return result;
250-
} else if (isImdsMsi) {
253+
} else if (isImdsMsi && this.sendProbeRequest) {
251254
// In the IMDS scenario we will probe the IMDS endpoint to ensure it's available before trying to get a token.
252255
// If the IMDS endpoint is not available and this is the source that MSAL will use, we will fail-fast with an error that tells DAC to move to the next credential.
253256
logger.getToken.info("Using the IMDS endpoint to probe for availability.");
@@ -268,7 +271,8 @@ export class ManagedIdentityCredential implements TokenCredential {
268271

269272
// If we got this far, it means:
270273
// - This is not a tokenExchangeMsi,
271-
// - We already probed for IMDS endpoint availability and failed-fast if it's unreachable.
274+
// - We already probed for IMDS endpoint availability and failed-fast if it's unreachable,
275+
// or we skip probing because the credential is set in DAC.
272276
// We can proceed normally by calling MSAL for a token.
273277
logger.getToken.info("Calling into MSAL for managed identity token.");
274278
const token = await this.managedIdentityApp.acquireToken({

sdk/identity/identity/src/credentials/managedIdentityCredential/options.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,20 @@ export interface ManagedIdentityCredentialObjectIdOptions extends TokenCredentia
4040
*/
4141
objectId: string;
4242
}
43+
44+
/**
45+
* @internal
46+
* Internal options for configuring the {@link ManagedIdentityCredential} with disable probe ability for DAC.
47+
* This type ensures that we can use any of the credential options (clientId, resourceId, or objectId)
48+
* along with the disableProbe flag for DefaultAzureCredential.
49+
*/
50+
export type InternalManagedIdentityCredentialOptions =
51+
| (ManagedIdentityCredentialClientIdOptions & ManagedIdentityDisableProbeOptions)
52+
| (ManagedIdentityCredentialResourceIdOptions & ManagedIdentityDisableProbeOptions)
53+
| (ManagedIdentityCredentialObjectIdOptions & ManagedIdentityDisableProbeOptions);
54+
55+
/**
56+
* Options for configuring Managed Identity Credential with disable probe.
57+
* This is only meant to use in DefaultAzureCredential when AZURE_TOKEN_CREDENTIALS is set to Managed Identity Credential.
58+
*/
59+
type ManagedIdentityDisableProbeOptions = { sendProbeRequest?: boolean };

sdk/identity/identity/test/internal/node/defaultAzureCredential.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe("DefaultAzureCredential", () => {
2626
`Invalid value for AZURE_TOKEN_CREDENTIALS = randomValue. Valid values are 'prod' or 'dev' or any of these credentials - EnvironmentCredential, WorkloadIdentityCredential, ManagedIdentityCredential, VisualStudioCodeCredential, AzureCliCredential, AzurePowerShellCredential, AzureDeveloperCliCredential.`,
2727
);
2828
});
29+
2930
it("should not throw an error if AZURE_TOKEN_CREDENTIALS is set to a supported value", () => {
3031
vi.stubEnv("AZURE_TOKEN_CREDENTIALS", "prod");
3132
expect(() => new DefaultAzureCredential()).not.toThrowError();
@@ -122,6 +123,12 @@ describe("create functions", () => {
122123
expect(cliSpy).not.toHaveBeenCalled();
123124
expect(devCliSpy).not.toHaveBeenCalled();
124125
expect(psSpy).not.toHaveBeenCalled();
126+
127+
expect(miSpy).toHaveBeenCalledWith(
128+
expect.objectContaining({
129+
sendProbeRequest: false,
130+
}),
131+
);
125132
});
126133

127134
it("calls only createDefaultWorkloadIdentityCredential when AZURE_TOKEN_CREDENTIALS is 'WorkloadIdentityCredential'", () => {
@@ -201,6 +208,12 @@ describe("create functions", () => {
201208

202209
expect(envSpy).toHaveBeenCalled();
203210
expect(miSpy).toHaveBeenCalled();
211+
// Ensure default MI behavior is passed in correctly to send probe request
212+
expect(miSpy).toHaveBeenCalledWith(
213+
expect.objectContaining({
214+
sendProbeRequest: true,
215+
}),
216+
);
204217
expect(wiSpy).toHaveBeenCalled();
205218
expect(vscSpy).not.toHaveBeenCalled();
206219
expect(cliSpy).not.toHaveBeenCalled();

sdk/identity/identity/test/internal/node/managedIdentityCredential/msalMsiProvider.spec.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { describe, it, assert, expect, vi, beforeEach, afterEach, type MockInsta
1616
import type { IdentityClient } from "$internal/client/identityClient.js";
1717
import { serviceFabricErrorMessage } from "$internal/credentials/managedIdentityCredential/utils.js";
1818
import { logger } from "@azure/identity";
19+
import { InternalManagedIdentityCredentialOptions } from "$internal/credentials/managedIdentityCredential/options.js";
1920

2021
describe("ManagedIdentityCredential (MSAL)", function () {
2122
let acquireTokenStub: MockInstance<
@@ -204,7 +205,7 @@ describe("ManagedIdentityCredential (MSAL)", function () {
204205
});
205206

206207
describe("when using IMDS", function () {
207-
it("probes the IMDS endpoint", async function () {
208+
it("should not probe the IMDS endpoint by default", async function () {
208209
vi.spyOn(
209210
ManagedIdentityApplication.prototype,
210211
"getManagedIdentitySource",
@@ -213,7 +214,33 @@ describe("ManagedIdentityCredential (MSAL)", function () {
213214

214215
const credential = new ManagedIdentityCredential();
215216
await credential.getToken("scope");
216-
expect(imdsIsAvailableStub).toHaveBeenCalledOnce();
217+
expect(imdsIsAvailableStub).not.toHaveBeenCalledOnce();
218+
});
219+
220+
it("skips IMDS probing when sendProbeRequest is false", async function () {
221+
vi.spyOn(
222+
ManagedIdentityApplication.prototype,
223+
"getManagedIdentitySource",
224+
).mockReturnValue("DefaultToImds");
225+
acquireTokenStub.mockResolvedValue(validAuthenticationResult as AuthenticationResult);
226+
227+
const options: InternalManagedIdentityCredentialOptions = { sendProbeRequest: false };
228+
const credential = new ManagedIdentityCredential(options);
229+
await credential.getToken("scope");
230+
expect(imdsIsAvailableStub).not.toHaveBeenCalled();
231+
});
232+
233+
it("probes IMDS when sendProbeRequest is true", async function () {
234+
vi.spyOn(
235+
ManagedIdentityApplication.prototype,
236+
"getManagedIdentitySource",
237+
).mockReturnValue("DefaultToImds");
238+
acquireTokenStub.mockResolvedValue(validAuthenticationResult as AuthenticationResult);
239+
240+
const options: InternalManagedIdentityCredentialOptions = { sendProbeRequest: true };
241+
const credential = new ManagedIdentityCredential(options);
242+
await credential.getToken("scope");
243+
expect(imdsIsAvailableStub).toHaveBeenCalled();
217244
});
218245
});
219246
});

0 commit comments

Comments
 (0)