Skip to content

Commit b0f4878

Browse files
authored
Handle Docker Desktop 403 response for IMDS endpoint (Azure#38321)
1 parent 48eb504 commit b0f4878

File tree

4 files changed

+45
-10
lines changed

4 files changed

+45
-10
lines changed

sdk/identity/Azure.Identity/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- `ManagedIdentityCredential` will fall through to the next credential in the chain in the case that Docker Desktop returns a 403 response when attempting to access the IMDS endpoint. [#38218](https://github.com/Azure/azure-sdk-for-net/issues/38218)
12+
1113
### Other Changes
1214

1315
## 1.10.0 (2023-08-14)

sdk/identity/Azure.Identity/src/ImdsManagedIdentitySource.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ internal ImdsManagedIdentitySource(ManagedIdentityClientOptions options) : base(
4040
_imdsNetworkTimeout = options.InitialImdsConnectionTimeout;
4141

4242
if (!string.IsNullOrEmpty(EnvironmentVariables.PodIdentityEndpoint))
43-
{
44-
var builder = new UriBuilder(EnvironmentVariables.PodIdentityEndpoint);
45-
builder.Path = imddsTokenPath;
43+
{
44+
var builder = new UriBuilder(EnvironmentVariables.PodIdentityEndpoint);
45+
builder.Path = imddsTokenPath;
4646
_imdsEndpoint = builder.Uri;
47-
}
48-
else
49-
{
50-
_imdsEndpoint = s_imdsEndpoint;
51-
}
47+
}
48+
else
49+
{
50+
_imdsEndpoint = s_imdsEndpoint;
51+
}
5252
}
5353

5454
protected override Request CreateRequest(string[] scopes)
@@ -103,6 +103,10 @@ public async override ValueTask<AccessToken> AuthenticateAsync(bool async, Token
103103
{
104104
throw new CredentialUnavailableException(AggregateError, e);
105105
}
106+
catch (CredentialUnavailableException)
107+
{
108+
throw;
109+
}
106110
}
107111

108112
protected override async ValueTask<AccessToken> HandleResponseAsync(bool async, TokenRequestContext context, Response response, CancellationToken cancellationToken)

sdk/identity/Azure.Identity/src/ManagedIdentitySource.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ protected virtual async ValueTask<AccessToken> HandleResponseAsync(
6767
exception = e;
6868
}
6969

70+
//This is a special case for Docker Desktop which responds with a 403 with a message that contains "A socket operation was attempted to an unreachable network"
71+
// rather than just timing out, as expected.
72+
if (response.Status == 403)
73+
{
74+
string message = response.Content.ToString();
75+
if (message.Contains("A socket operation was attempted to an unreachable network"))
76+
{
77+
throw new CredentialUnavailableException(UnexpectedResponse, new Exception(message));
78+
}
79+
}
80+
7081
throw new RequestFailedException(response, exception);
7182
}
7283

sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,24 @@ public void VerifyImdsRequestFailurePopulatesExceptionMessage()
277277
Assert.That(ex.Message, Does.Contain(expectedMessage));
278278
}
279279

280+
[NonParallelizable]
281+
[Test]
282+
public void VerifyImdsRequestFailureForDockerDesktopThrowsCUE()
283+
{
284+
using var environment = new TestEnvVar(new() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", null }, { "IDENTITY_HEADER", null }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });
285+
286+
var expectedMessage = "connecting to 169.254.169.254:80: connecting to 169.254.169.254:80: dial tcp 169.254.169.254:80: connectex: A socket operation was attempted to an unreachable network.";
287+
var response = CreateInvalidJsonResponse(403, expectedMessage);
288+
var mockTransport = new MockTransport(response);
289+
var options = new TokenCredentialOptions() { Transport = mockTransport };
290+
var pipeline = CredentialPipeline.GetInstance(options);
291+
292+
ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential("mock-client-id", pipeline));
293+
294+
var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));
295+
Assert.That(ex.InnerException.Message, Does.Contain(expectedMessage));
296+
}
297+
280298
[NonParallelizable]
281299
[Test]
282300
public void VerifyImdsRequestFailureWithInvalidJsonPopulatesExceptionMessage()
@@ -920,10 +938,10 @@ private MockResponse CreateErrorMockResponse(int responseCode, string message)
920938
return response;
921939
}
922940

923-
private static MockResponse CreateInvalidJsonResponse(int status)
941+
private static MockResponse CreateInvalidJsonResponse(int status, string message = "invalid json")
924942
{
925943
var response = new MockResponse(status);
926-
response.SetContent("invalid json");
944+
response.SetContent(message);
927945
return response;
928946
}
929947
}

0 commit comments

Comments
 (0)