Skip to content

Commit

Permalink
[Identity] Hotfix 1.2.4 (#14123)
Browse files Browse the repository at this point in the history
I'll release on Monday. In the mean time, I'll merge this and set up a release build so I can test the artifacts.
  • Loading branch information
sadasant authored Mar 6, 2021
1 parent f527dc3 commit 527a7b3
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 20 deletions.
5 changes: 5 additions & 0 deletions sdk/identity/identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release History

## 1.2.4 (2021-03-08)

- Bug fix: Now if the `managedIdentityClientId` optional parameter is provided to `DefaultAzureCredential`, it will be properly passed through to the underlying `ManagedIdentityCredential`. Related to customer issue: [13872](https://github.com/Azure/azure-sdk-for-js/issues/13872).
- Bug fix: `ManagedIdentityCredential` now also properly handles `EHOSTUNREACH` errors. Fixes issue [13894](https://github.com/Azure/azure-sdk-for-js/issues/13894).

## 1.2.3 (2021-02-09)

- Fixed Azure Stack support for the NodeJS version of the `InteractiveBrowserCredential`.
Expand Down
2 changes: 1 addition & 1 deletion sdk/identity/identity/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@azure/identity",
"sdk-type": "client",
"version": "1.2.3",
"version": "1.2.4",
"description": "Provides credential implementations for Azure SDK libraries that can authenticate with Azure Active Directory",
"main": "dist/index.js",
"module": "dist-esm/src/index.js",
Expand Down
6 changes: 4 additions & 2 deletions sdk/identity/identity/rollup.base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export function nodeConfig(test = false) {
baseConfig.input = [
"dist-esm/test/public/*.spec.js",
"dist-esm/test/public/node/*.spec.js",
"dist-esm/test/internal/*.spec.js"
"dist-esm/test/internal/*.spec.js",
"dist-esm/test/internal/node/*.spec.js"
];
baseConfig.plugins.unshift(multiEntry({ exports: false }));

Expand Down Expand Up @@ -97,7 +98,8 @@ export function browserConfig(test = false) {
baseConfig.input = [
"dist-esm/test/public/*.spec.js",
"dist-esm/test/public/browser/*.spec.js",
"dist-esm/test/internal/*.spec.js"
"dist-esm/test/internal/*.spec.js",
"dist-esm/test/internal/browser/*.spec.js"
];
baseConfig.plugins.unshift(multiEntry({ exports: false }));
baseConfig.output.file = "test-browser/index.js";
Expand Down
17 changes: 11 additions & 6 deletions sdk/identity/identity/src/credentials/defaultAzureCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,20 @@ export class DefaultAzureCredential extends ChainedTokenCredential {
constructor(tokenCredentialOptions?: DefaultAzureCredentialOptions) {
const credentials = [];
credentials.push(new EnvironmentCredential(tokenCredentialOptions));
credentials.push(new ManagedIdentityCredential(tokenCredentialOptions));
if (process.env.AZURE_CLIENT_ID) {

// In case a user assigned ID has been provided.
const managedIdentityClientId =
tokenCredentialOptions?.managedIdentityClientId || process.env.AZURE_CLIENT_ID;

if (managedIdentityClientId) {
credentials.push(
new ManagedIdentityCredential(
tokenCredentialOptions?.managedIdentityClientId || process.env.AZURE_CLIENT_ID,
tokenCredentialOptions
)
new ManagedIdentityCredential(managedIdentityClientId, tokenCredentialOptions)
);
} else {
// If the user didn't provide an ID, we'll try with a system assigned ID.
credentials.push(new ManagedIdentityCredential(tokenCredentialOptions));
}

credentials.push(new AzureCliCredential());
credentials.push(new VisualStudioCodeCredential(tokenCredentialOptions));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,20 @@ export class ManagedIdentityCredential implements TokenCredential {
message: err.message
});

// If either the network is unreachable,
// we can safely assume the credential is unavailable.
if (err.code === "ENETUNREACH") {
const error = new CredentialUnavailable(
"ManagedIdentityCredential is unavailable. Network unreachable."
);

logger.getToken.info(formatError(scopes, error));
throw error;
}

// If either the host was unreachable,
// we can safely assume the credential is unavailable.
if (err.code === "EHOSTUNREACH") {
const error = new CredentialUnavailable(
"ManagedIdentityCredential is unavailable. No managed identity endpoint found."
);
Expand All @@ -213,6 +226,15 @@ export class ManagedIdentityCredential implements TokenCredential {
);
}

// If the error has no status code, we can assume there was no available identity.
// This will throw silently during any ChainedTokenCredential.
if (err.statusCode === undefined) {
throw new CredentialUnavailable(
`ManagedIdentityCredential authentication failed. Message ${err.message}`
);
}

// Any other error should break the chain.
throw new AuthenticationError(err.statusCode, {
error: "ManagedIdentityCredential authentication failed.",
error_description: err.message
Expand Down
20 changes: 15 additions & 5 deletions sdk/identity/identity/test/authTestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
import * as coreHttp from "@azure/core-http";

export interface MockAuthResponse {
status: number;
status?: number;
error?: RestError;
headers?: HttpHeaders;
parsedBody?: any;
bodyAsText?: string;
Expand All @@ -27,7 +28,7 @@ export interface MockAuthHttpClientOptions {

export class MockAuthHttpClient implements HttpClient {
private authResponses: MockAuthResponse[] = [];
private currentResponse: number = 0;
private currentResponseIndex: number = 0;
private mockTimeout: boolean;

public tokenCredentialOptions: ClientCertificateCredentialOptions;
Expand Down Expand Up @@ -76,13 +77,22 @@ export class MockAuthHttpClient implements HttpClient {
throw new Error("The number of requests has exceeded the number of authResponses");
}

const authResponse = this.authResponses[this.currentResponseIndex];

if (authResponse.error) {
this.currentResponseIndex++;
throw authResponse.error;
}

const response = {
request: httpRequest,
headers: this.authResponses[this.currentResponse].headers || new HttpHeaders(),
...this.authResponses[this.currentResponse]
headers: authResponse.headers || new HttpHeaders(),
status: authResponse.status || 200,
parsedBody: authResponse.parsedBody,
bodyAsText: authResponse.bodyAsText
};

this.currentResponse++;
this.currentResponseIndex++;
return response;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import {
imdsApiVersion
} from "../../../src/credentials/managedIdentityCredential/constants";
import { MockAuthHttpClient, MockAuthHttpClientOptions, assertRejects } from "../../authTestUtils";
import { WebResource, AccessToken, HttpHeaders } from "@azure/core-http";
import { WebResource, AccessToken, HttpHeaders, RestError } from "@azure/core-http";
import { OAuthErrorResponse } from "../../../src/client/errors";

interface AuthRequestDetails {
requests: WebResource[];
token: AccessToken | null;
}

describe("ManagedIdentityCredential", function() {
describe("ManagedIdentityCredential", function () {
afterEach(() => {
delete process.env.IDENTITY_ENDPOINT;
delete process.env.IDENTITY_HEADER;
Expand All @@ -26,7 +26,7 @@ describe("ManagedIdentityCredential", function() {
delete process.env.IDENTITY_SERVER_THUMBPRINT;
});

it("sends an authorization request with a modified resource name", async function() {
it("sends an authorization request with a modified resource name", async function () {
const authDetails = await getMsiTokenAuthRequest(["https://service/.default"], "client", {
authResponse: [
{ status: 200 }, // Respond to IMDS isAvailable
Expand Down Expand Up @@ -83,7 +83,24 @@ describe("ManagedIdentityCredential", function() {
}
});

it("returns error when ManagedIdentityCredential authentication failed", async function() {
it("returns error when no MSI is available", async function () {
process.env.AZURE_CLIENT_ID = "errclient";

const imdsError: RestError = new RestError("Request Timeout", "REQUEST_SEND_ERROR", 408);
const mockHttpClient = new MockAuthHttpClient({
authResponse: [{ error: imdsError }]
});

const credential = new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID, {
...mockHttpClient.tokenCredentialOptions
});
await assertRejects(
credential.getToken("scopes"),
(error: AuthenticationError) => error.message.indexOf("No MSI credential available") > -1
);
});

it("an unexpected error bubbles all the way up", async function () {
process.env.AZURE_CLIENT_ID = "errclient";

const errResponse: OAuthErrorResponse = {
Expand All @@ -92,7 +109,41 @@ describe("ManagedIdentityCredential", function() {
};

const mockHttpClient = new MockAuthHttpClient({
authResponse: [{ status: 400, parsedBody: errResponse }]
authResponse: [{ status: 200 }, { status: 500, parsedBody: errResponse }]
});

const credential = new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID, {
...mockHttpClient.tokenCredentialOptions
});
await assertRejects(
credential.getToken("scopes"),
(error: AuthenticationError) => error.message.indexOf(errResponse.error) > -1
);
});

it("returns expected error when the network was unreachable", async function () {
process.env.AZURE_CLIENT_ID = "errclient";

const netError: RestError = new RestError("Request Timeout", "ENETUNREACH", 408);
const mockHttpClient = new MockAuthHttpClient({
authResponse: [{ status: 200 }, { error: netError }]
});

const credential = new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID, {
...mockHttpClient.tokenCredentialOptions
});
await assertRejects(
credential.getToken("scopes"),
(error: AuthenticationError) => error.message.indexOf("Network unreachable.") > -1
);
});

it("returns expected error when the host was unreachable", async function () {
process.env.AZURE_CLIENT_ID = "errclient";

const hostError: RestError = new RestError("Request Timeout", "EHOSTUNREACH", 408);
const mockHttpClient = new MockAuthHttpClient({
authResponse: [{ status: 200 }, { error: hostError }]
});

const credential = new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID, {
Expand All @@ -101,7 +152,7 @@ describe("ManagedIdentityCredential", function() {
await assertRejects(
credential.getToken("scopes"),
(error: AuthenticationError) =>
error.errorResponse.error.indexOf("ManagedIdentityCredential authentication failed.") > -1
error.message.indexOf("No managed identity endpoint found.") > -1
);
});

Expand Down

0 comments on commit 527a7b3

Please sign in to comment.