-
Notifications
You must be signed in to change notification settings - Fork 4.5k
/
ImdsManagedIdentitySource.cs
132 lines (110 loc) · 5.41 KB
/
ImdsManagedIdentitySource.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;
namespace Azure.Identity
{
internal class ImdsManagedIdentitySource : IManagedIdentitySource
{
// IMDS constants. Docs for IMDS are available here https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
private static readonly Uri s_imdsEndpoint = new Uri("http://169.254.169.254/metadata/identity/oauth2/token");
private static readonly IPAddress s_imdsHostIp = IPAddress.Parse("169.254.169.254");
private const int s_imdsPort = 80;
private const int ImdsAvailableTimeoutMs = 1000;
private const string ImdsApiVersion = "2018-02-01";
internal const string IdentityUnavailableError = "ManagedIdentityCredential authentication unavailable. The requested identity has not been assigned to this resource.";
private readonly HttpPipeline _pipeline;
private readonly string _clientId;
private string _identityUnavailableErrorMessage;
public static async ValueTask<IManagedIdentitySource> TryCreateAsync(HttpPipeline pipeline, string clientId, bool async, CancellationToken cancellationToken)
{
AzureIdentityEventSource.Singleton.ProbeImdsEndpoint(s_imdsEndpoint);
bool available;
// try to create a TCP connection to the IMDS IP address. If the connection can be established
// we assume that IMDS is available. If connecting times out or fails to connect assume that
// IMDS is not available in this environment.
try
{
using var client = new TcpClient();
Task connectTask = client.ConnectAsync(s_imdsHostIp, s_imdsPort);
if (async)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(ImdsAvailableTimeoutMs);
await connectTask.AwaitWithCancellation(cts.Token);
available = client.Connected;
}
else
{
available = connectTask.Wait(ImdsAvailableTimeoutMs, cancellationToken) && client.Connected;
}
}
catch
{
available = false;
}
if (available)
{
AzureIdentityEventSource.Singleton.ImdsEndpointFound(s_imdsEndpoint);
}
else
{
AzureIdentityEventSource.Singleton.ImdsEndpointUnavailable(s_imdsEndpoint);
}
return available ? new ImdsManagedIdentitySource(pipeline, clientId) : default;
}
internal ImdsManagedIdentitySource(HttpPipeline pipeline, string clientId)
{
_pipeline = pipeline;
_clientId = clientId;
}
public Request CreateRequest(string[] scopes)
{
if (_identityUnavailableErrorMessage != default)
{
throw new CredentialUnavailableException(_identityUnavailableErrorMessage);
}
// covert the scopes to a resource string
string resource = ScopeUtilities.ScopesToResource(scopes);
Request request = _pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Headers.Add("Metadata", "true");
request.Uri.Reset(s_imdsEndpoint);
request.Uri.AppendQuery("api-version", ImdsApiVersion);
request.Uri.AppendQuery("resource", resource);
if (!string.IsNullOrEmpty(_clientId))
{
request.Uri.AppendQuery("client_id", _clientId);
}
return request;
}
public AccessToken GetAccessTokenFromJson(in JsonElement jsonAccessToken, in JsonElement jsonExpiresOn)
{
// the seconds from epoch may be returned as a Json number or a Json string which is a number
// depending on the environment. If neither of these are the case we throw an AuthException.
if (jsonExpiresOn.ValueKind == JsonValueKind.Number && jsonExpiresOn.TryGetInt64(out long expiresOnSec) ||
jsonExpiresOn.ValueKind == JsonValueKind.String && long.TryParse(jsonExpiresOn.GetString(), out expiresOnSec))
{
return new AccessToken(jsonAccessToken.GetString(), DateTimeOffset.FromUnixTimeSeconds(expiresOnSec));
}
throw new AuthenticationFailedException(ManagedIdentityClient.AuthenticationResponseInvalidFormatError);
}
public async ValueTask HandleFailedRequestAsync(Response response, ClientDiagnostics diagnostics, bool async)
{
if (response.Status == 400)
{
string message = _identityUnavailableErrorMessage ?? await diagnostics
.CreateRequestFailedMessageAsync(response, IdentityUnavailableError, null, null, async)
.ConfigureAwait(false);
Interlocked.CompareExchange(ref _identityUnavailableErrorMessage, message, null);
throw new CredentialUnavailableException(message);
}
}
}
}