Så här använder du ett Service Fabric-programs hanterade identitet för att få åtkomst till Azure-tjänster
Service Fabric-program kan använda hanterade identiteter för att få åtkomst till andra Azure-resurser som stöder Microsoft Entra ID-baserad autentisering. Ett program kan hämta en åtkomsttoken som representerar dess identitet, som kan vara systemtilldelad eller användartilldelad, och använda den som en "ägartoken" för att autentisera sig till en annan tjänst , även kallad en skyddad resursserver. Token representerar den identitet som tilldelats Service Fabric-programmet och utfärdas endast till Azure-resurser (inklusive SF-program) som delar den identiteten. Se översiktsdokumentationen för hanterad identitet för en detaljerad beskrivning av hanterade identiteter, samt skillnaden mellan systemtilldelade och användartilldelade identiteter. Vi refererar till ett Service Fabric-program med hanterad identitet som klientprogram i den här artikeln.
Se ett kompletterande exempelprogram som demonstrerar användning av systemtilldelade och användartilldelade hanterade Service Fabric-programidentiteter med Reliable Services och containrar.
Viktigt!
En hanterad identitet representerar associationen mellan en Azure-resurs och ett huvudnamn för tjänsten i motsvarande Microsoft Entra-klientorganisation som är associerad med prenumerationen som innehåller resursen. I samband med Service Fabric stöds därför endast hanterade identiteter för program som distribueras som Azure-resurser.
Viktigt!
Innan du använder den hanterade identiteten för ett Service Fabric-program måste klientprogrammet beviljas åtkomst till den skyddade resursen. Se listan över Azure-tjänster som stöder Microsoft Entra-autentisering för att söka efter support och sedan till respektive tjänsts dokumentation för specifika steg för att bevilja en identitet åtkomst till resurser av intresse.
Utnyttja en hanterad identitet med hjälp av Azure.Identity
Azure Identity SDK stöder nu Service Fabric. Med Azure.Identity blir det enklare att skriva kod för att använda hanterade identiteter i Service Fabric-appen eftersom den hanterar hämtning av token, cachelagringstoken och serverautentisering. När du har åtkomst till de flesta Azure-resurser är begreppet token dolt.
Service Fabric-stöd är tillgängligt i följande versioner för dessa språk:
- C# i version 1.3.0. Se ett C#-exempel.
- Python i version 1.5.0. Se ett Python-exempel.
- Java i version 1.2.0.
C#-exempel på att initiera autentiseringsuppgifter och använda autentiseringsuppgifterna för att hämta en hemlighet från Azure Key Vault:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace MyMIService
{
internal sealed class MyMIService : StatelessService
{
protected override async Task RunAsync(CancellationToken cancellationToken)
{
try
{
// Load the service fabric application managed identity assigned to the service
ManagedIdentityCredential creds = new ManagedIdentityCredential();
// Create a client to keyvault using that identity
SecretClient client = new SecretClient(new Uri("https://mykv.vault.azure.net/"), creds);
// Fetch a secret
KeyVaultSecret secret = (await client.GetSecretAsync("mysecret", cancellationToken: cancellationToken)).Value;
}
catch (CredentialUnavailableException e)
{
// Handle errors with loading the Managed Identity
}
catch (RequestFailedException)
{
// Handle errors with fetching the secret
}
catch (Exception e)
{
// Handle generic errors
}
}
}
}
Hämta en åtkomsttoken med hjälp av REST API
I kluster som är aktiverade för hanterad identitet exponerar Service Fabric-körningen en localhost-slutpunkt som program kan använda för att hämta åtkomsttoken. Slutpunkten är tillgänglig på varje nod i klustret och är tillgänglig för alla entiteter på den noden. Auktoriserade uppringare kan hämta åtkomsttoken genom att anropa den här slutpunkten och presentera en autentiseringskod. koden genereras av Service Fabric-körningen för varje distinkt aktivering av tjänstkodpaket och är bunden till livslängden för processen som är värd för tjänstkodpaketet.
Mer specifikt kommer miljön för en hanterad identitetsaktiverad Service Fabric-tjänst att seedas med följande variabler:
- "IDENTITY_ENDPOINT": den localhost-slutpunkt som motsvarar tjänstens hanterade identitet
- "IDENTITY_HEADER": en unik autentiseringskod som representerar tjänsten på den aktuella noden
- "IDENTITY_SERVER_THUMBPRINT": Tumavtryck för den hanterade identitetsservern för Service Fabric
Viktigt!
Programkoden bör betrakta värdet för miljövariabeln "IDENTITY_HEADER" som känsliga data – den bör inte loggas eller spridas på annat sätt. Autentiseringskoden har inget värde utanför den lokala noden eller när processen som är värd för tjänsten har avslutats, men den representerar identiteten för Service Fabric-tjänsten och bör därför behandlas med samma försiktighetsåtgärder som själva åtkomsttoken.
Klienten utför följande steg för att hämta en token:
- bildar en URI genom att sammanfoga slutpunkten för hanterad identitet (IDENTITY_ENDPOINT värde) med API-versionen och den resurs (målgrupp) som krävs för token
- skapar en GET http(s)-begäran för den angivna URI:n
- lägger till lämplig verifieringslogik för servercertifikat
- lägger till autentiseringskoden (IDENTITY_HEADER värde) som ett huvud i begäran
- skickar begäran
Ett lyckat svar innehåller en JSON-nyttolast som representerar den resulterande åtkomsttoken samt metadata som beskriver den. Ett misslyckat svar innehåller också en förklaring av felet. Mer information om felhantering finns nedan.
Åtkomsttoken cachelagras av Service Fabric på olika nivåer (nod, kluster, resursprovidertjänst), så ett lyckat svar innebär inte nödvändigtvis att token utfärdades direkt som svar på användarprogrammets begäran. Token cachelagras under mindre än deras livslängd, så ett program garanterat får en giltig token. Vi rekommenderar att programkoden cachelagrar sig alla åtkomsttoken som den hämtar. cachelagringsnyckeln ska innehålla (en härledning av) målgruppen.
Exempelförfrågan:
GET 'https://localhost:2377/metadata/identity/oauth2/token?api-version=2019-07-01-preview&resource=https://vault.azure.net/' HTTP/1.1 Secret: 912e4af7-77ba-4fa5-a737-56c8e3ace132
där:
Element | Description |
---|---|
GET |
HTTP-verbet som anger att du vill hämta data från slutpunkten. I det här fallet en OAuth-åtkomsttoken. |
https://localhost:2377/metadata/identity/oauth2/token |
Den hanterade identitetsslutpunkten för Service Fabric-program som tillhandahålls via miljövariabeln IDENTITY_ENDPOINT. |
api-version |
En frågesträngsparameter som anger API-versionen av tjänsten för hanterad identitetstoken. för närvarande är 2019-07-01-preview det enda godkända värdet , och kan komma att ändras. |
resource |
En frågesträngsparameter som anger målresursens app-ID-URI. Detta återspeglas som anspråket (målgruppen aud ) för den utfärdade token. I det här exemplet begärs en token för åtkomst till Azure Key Vault, vars app-ID-URI är https://vault.azure.net/. |
Secret |
Ett rubrikfält för HTTP-begäran som krävs av Service Fabric Managed Identity Token Service för Service Fabric-tjänster för att autentisera anroparen. Det här värdet tillhandahålls av SF-körningen via IDENTITY_HEADER miljövariabel. |
Exempelsvar:
HTTP/1.1 200 OK
Content-Type: application/json
{
"token_type": "Bearer",
"access_token": "eyJ0eXAiO...",
"expires_on": 1565244611,
"resource": "https://vault.azure.net/"
}
där:
Element | Description |
---|---|
token_type |
Typ av token; I det här fallet är en "Bearer"-åtkomsttoken, vilket innebär att presentatören ('bearer') för denna token är det avsedda ämnet för token. |
access_token |
Den begärda åtkomsttoken. När du anropar ett skyddat REST-API bäddas token in i Authorization fältet för begärandehuvud som en "ägartoken", vilket gör att API:et kan autentisera anroparen. |
expires_on |
Tidsstämpeln för förfallodatumet för åtkomsttoken. representeras som antalet sekunder från "1970-01-01T0:0:0Z UTC" och motsvarar tokens exp anspråk. I det här fallet upphör token att gälla 2019-08-08T06:10:11+00:00 (i RFC 3339) |
resource |
Resursen som åtkomsttoken utfärdades för, angiven via frågesträngsparametern resource för begäran, motsvarar tokens "aud"-anspråk. |
Hämta en åtkomsttoken med C#
Ovanstående blir i C#:
namespace Azure.ServiceFabric.ManagedIdentity.Samples
{
using System;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
/// <summary>
/// Type representing the response of the SF Managed Identity endpoint for token acquisition requests.
/// </summary>
[JsonObject]
public sealed class ManagedIdentityTokenResponse
{
[JsonProperty(Required = Required.Always, PropertyName = "token_type")]
public string TokenType { get; set; }
[JsonProperty(Required = Required.Always, PropertyName = "access_token")]
public string AccessToken { get; set; }
[JsonProperty(PropertyName = "expires_on")]
public string ExpiresOn { get; set; }
[JsonProperty(PropertyName = "resource")]
public string Resource { get; set; }
}
/// <summary>
/// Sample class demonstrating access token acquisition using Managed Identity.
/// </summary>
public sealed class AccessTokenAcquirer
{
/// <summary>
/// Acquire an access token.
/// </summary>
/// <returns>Access token</returns>
public static async Task<string> AcquireAccessTokenAsync()
{
var managedIdentityEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
var managedIdentityAuthenticationCode = Environment.GetEnvironmentVariable("IDENTITY_HEADER");
var managedIdentityServerThumbprint = Environment.GetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT");
// Latest api version, 2019-07-01-preview is still supported.
var managedIdentityApiVersion = Environment.GetEnvironmentVariable("IDENTITY_API_VERSION");
var managedIdentityAuthenticationHeader = "secret";
var resource = "https://management.azure.com/";
var requestUri = $"{managedIdentityEndpoint}?api-version={managedIdentityApiVersion}&resource={HttpUtility.UrlEncode(resource)}";
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
requestMessage.Headers.Add(managedIdentityAuthenticationHeader, managedIdentityAuthenticationCode);
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, certChain, policyErrors) =>
{
// Do any additional validation here
if (policyErrors == SslPolicyErrors.None)
{
return true;
}
return 0 == string.Compare(cert.GetCertHashString(), managedIdentityServerThumbprint, StringComparison.OrdinalIgnoreCase);
};
try
{
var response = await new HttpClient(handler).SendAsync(requestMessage)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var tokenResponseString = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
var tokenResponseObject = JsonConvert.DeserializeObject<ManagedIdentityTokenResponse>(tokenResponseString);
return tokenResponseObject.AccessToken;
}
catch (Exception ex)
{
string errorText = String.Format("{0} \n\n{1}", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "Acquire token failed");
Console.WriteLine(errorText);
}
return String.Empty;
}
} // class AccessTokenAcquirer
} // namespace Azure.ServiceFabric.ManagedIdentity.Samples
Komma åt Key Vault från ett Service Fabric-program med hjälp av hanterad identitet
Det här exemplet bygger på ovanstående för att demonstrera åtkomst till en hemlighet som lagras i ett Key Vault med hjälp av hanterad identitet.
/// <summary>
/// Probe the specified secret, displaying metadata on success.
/// </summary>
/// <param name="vault">vault name</param>
/// <param name="secret">secret name</param>
/// <param name="version">secret version id</param>
/// <returns></returns>
public async Task<string> ProbeSecretAsync(string vault, string secret, string version)
{
// initialize a KeyVault client with a managed identity-based authentication callback
var kvClient = new Microsoft.Azure.KeyVault.KeyVaultClient(new Microsoft.Azure.KeyVault.KeyVaultClient.AuthenticationCallback((a, r, s) => { return AuthenticationCallbackAsync(a, r, s); }));
Log(LogLevel.Info, $"\nRunning with configuration: \n\tobserved vault: {config.VaultName}\n\tobserved secret: {config.SecretName}\n\tMI endpoint: {config.ManagedIdentityEndpoint}\n\tMI auth code: {config.ManagedIdentityAuthenticationCode}\n\tMI auth header: {config.ManagedIdentityAuthenticationHeader}");
string response = String.Empty;
Log(LogLevel.Info, "\n== {DateTime.UtcNow.ToString()}: Probing secret...");
try
{
var secretResponse = await kvClient.GetSecretWithHttpMessagesAsync(vault, secret, version)
.ConfigureAwait(false);
if (secretResponse.Response.IsSuccessStatusCode)
{
// use the secret: secretValue.Body.Value;
response = String.Format($"Successfully probed secret '{secret}' in vault '{vault}': {PrintSecretBundleMetadata(secretResponse.Body)}");
}
else
{
response = String.Format($"Non-critical error encountered retrieving secret '{secret}' in vault '{vault}': {secretResponse.Response.ReasonPhrase} ({secretResponse.Response.StatusCode})");
}
}
catch (Microsoft.Rest.ValidationException ve)
{
response = String.Format($"encountered REST validation exception 0x{ve.HResult.ToString("X")} trying to access '{secret}' in vault '{vault}' from {ve.Source}: {ve.Message}");
}
catch (KeyVaultErrorException kvee)
{
response = String.Format($"encountered KeyVault exception 0x{kvee.HResult.ToString("X")} trying to access '{secret}' in vault '{vault}': {kvee.Response.ReasonPhrase} ({kvee.Response.StatusCode})");
}
catch (Exception ex)
{
// handle generic errors here
response = String.Format($"encountered exception 0x{ex.HResult.ToString("X")} trying to access '{secret}' in vault '{vault}': {ex.Message}");
}
Log(LogLevel.Info, response);
return response;
}
/// <summary>
/// KV authentication callback, using the application's managed identity.
/// </summary>
/// <param name="authority">The expected issuer of the access token, from the KV authorization challenge.</param>
/// <param name="resource">The expected audience of the access token, from the KV authorization challenge.</param>
/// <param name="scope">The expected scope of the access token; not currently used.</param>
/// <returns>Access token</returns>
public async Task<string> AuthenticationCallbackAsync(string authority, string resource, string scope)
{
Log(LogLevel.Verbose, $"authentication callback invoked with: auth: {authority}, resource: {resource}, scope: {scope}");
var encodedResource = HttpUtility.UrlEncode(resource);
// This sample does not illustrate the caching of the access token, which the user application is expected to do.
// For a given service, the caching key should be the (encoded) resource uri. The token should be cached for a period
// of time at most equal to its remaining validity. The 'expires_on' field of the token response object represents
// the number of seconds from Unix time when the token will expire. You may cache the token if it will be valid for at
// least another short interval (1-10s). If its expiration will occur shortly, don't cache but still return it to the
// caller. The MI endpoint will not return an expired token.
// Sample caching code:
//
// ManagedIdentityTokenResponse tokenResponse;
// if (responseCache.TryGetCachedItem(encodedResource, out tokenResponse))
// {
// Log(LogLevel.Verbose, $"cache hit for key '{encodedResource}'");
//
// return tokenResponse.AccessToken;
// }
//
// Log(LogLevel.Verbose, $"cache miss for key '{encodedResource}'");
//
// where the response cache is left as an exercise for the reader. MemoryCache is a good option, albeit not yet available on .net core.
var requestUri = $"{config.ManagedIdentityEndpoint}?api-version={config.ManagedIdentityApiVersion}&resource={encodedResource}";
Log(LogLevel.Verbose, $"request uri: {requestUri}");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
requestMessage.Headers.Add(config.ManagedIdentityAuthenticationHeader, config.ManagedIdentityAuthenticationCode);
Log(LogLevel.Verbose, $"added header '{config.ManagedIdentityAuthenticationHeader}': '{config.ManagedIdentityAuthenticationCode}'");
var response = await httpClient.SendAsync(requestMessage)
.ConfigureAwait(false);
Log(LogLevel.Verbose, $"response status: success: {response.IsSuccessStatusCode}, status: {response.StatusCode}");
response.EnsureSuccessStatusCode();
var tokenResponseString = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
var tokenResponse = JsonConvert.DeserializeObject<ManagedIdentityTokenResponse>(tokenResponseString);
Log(LogLevel.Verbose, "deserialized token response; returning access code..");
// Sample caching code (continuation):
// var expiration = DateTimeOffset.FromUnixTimeSeconds(Int32.Parse(tokenResponse.ExpiresOn));
// if (expiration > DateTimeOffset.UtcNow.AddSeconds(5.0))
// responseCache.AddOrUpdate(encodedResource, tokenResponse, expiration);
return tokenResponse.AccessToken;
}
private string PrintSecretBundleMetadata(SecretBundle bundle)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.AppendFormat($"\n\tid: {bundle.Id}\n");
strBuilder.AppendFormat($"\tcontent type: {bundle.ContentType}\n");
strBuilder.AppendFormat($"\tmanaged: {bundle.Managed}\n");
strBuilder.AppendFormat($"\tattributes:\n");
strBuilder.AppendFormat($"\t\tenabled: {bundle.Attributes.Enabled}\n");
strBuilder.AppendFormat($"\t\tnbf: {bundle.Attributes.NotBefore}\n");
strBuilder.AppendFormat($"\t\texp: {bundle.Attributes.Expires}\n");
strBuilder.AppendFormat($"\t\tcreated: {bundle.Attributes.Created}\n");
strBuilder.AppendFormat($"\t\tupdated: {bundle.Attributes.Updated}\n");
strBuilder.AppendFormat($"\t\trecoveryLevel: {bundle.Attributes.RecoveryLevel}\n");
return strBuilder.ToString();
}
private enum LogLevel
{
Info,
Verbose
};
private void Log(LogLevel level, string message)
{
if (level != LogLevel.Verbose
|| config.DoVerboseLogging)
{
Console.WriteLine(message);
}
}
Felhantering
Fältet statuskod i HTTP-svarshuvudet anger statusen för begäran. statusen "200 OK" anger att det lyckades och svaret innehåller åtkomsttoken enligt beskrivningen ovan. Följande är en kort uppräkning av möjliga felsvar.
Statuskod | Felorsak | Hantera |
---|---|---|
404 Hittades inte. | Okänd autentiseringskod, eller så tilldelades inte programmet någon hanterad identitet. | Korrigera programkonfigurationen eller tokeninsamlingskoden. |
429 För många förfrågningar. | Begränsningsgränsen har nåtts, som införts av Microsoft Entra ID eller SF. | Försök igen med exponentiell backoff. Se vägledningen nedan. |
4xx Fel i begäran. | En eller flera av begärandeparametrarna var felaktiga. | Försök inte igen. Mer information finns i felinformationen. 4xx-fel är designtidsfel. |
5xx Fel från tjänsten. | Undersystemet för hanterad identitet eller Microsoft Entra-ID returnerade ett tillfälligt fel. | Det är säkert att försöka igen efter en kort stund. Du kan stöta på ett begränsningsvillkor (429) när du försöker igen. |
Om ett fel inträffar innehåller motsvarande HTTP-svarstext ett JSON-objekt med felinformationen:
Element | Description |
---|---|
kod | Felkod. |
correlationId | Ett korrelations-ID som kan användas för felsökning. |
meddelande | Utförlig beskrivning av felet. Felbeskrivningar kan ändras när som helst. Var inte beroende av själva felmeddelandet. |
Exempelfel:
{"error":{"correlationId":"7f30f4d3-0f3a-41e0-a417-527f21b3848f","code":"SecretHeaderNotFound","message":"Secret is not found in the request headers."}}
Här följer en lista över vanliga Service Fabric-fel som är specifika för hanterade identiteter:
Kod | Meddelande | beskrivning |
---|---|---|
SecretHeaderNotFound | Hemligheten hittades inte i begäranderubrikerna. | Autentiseringskoden tillhandahölls inte med begäran. |
ManagedIdentityNotFound | Det gick inte att hitta den hanterade identiteten för den angivna programvärden. | Programmet har ingen identitet eller så är autentiseringskoden okänd. |
ArgumentNullOrEmpty | Parametern "resurs" får inte vara null eller tom sträng. | Resursen (målgruppen) angavs inte i begäran. |
InvalidApiVersion | API-versionen stöds inte. Versionen som stöds är "2019-07-01-preview". | Api-version som saknas eller som inte stöds anges i begärande-URI:n. |
InternalServerError | Ett fel har inträffat. | Ett fel påträffades i undersystemet för hanterad identitet, eventuellt utanför Service Fabric-stacken. Den troligaste orsaken är ett felaktigt värde som angetts för resursen (sök efter avslutande '/'?) |
Vägledning för återförsök
Vanligtvis är den enda återförsöksbara felkoden 429 (för många begäranden); interna serverfel/5xx-felkoder kan försöka igen, men orsaken kan vara permanent.
Begränsningsgränser gäller för antalet anrop som görs till undersystemet för hanterad identitet – särskilt de "överordnade" beroendena (Azure-tjänsten för hanterad identitet eller tjänsten för säker token). Service Fabric cachelagrar token på olika nivåer i pipelinen, men med tanke på de berörda komponenternas distribuerade karaktär kan anroparen uppleva inkonsekventa begränsningssvar (d.v.s. begränsas på en nod/instans av ett program, men inte på en annan nod när en token begärs för samma identitet.) När begränsningsvillkoret har angetts kan efterföljande begäranden från samma program misslyckas med HTTP-statuskoden 429 (för många begäranden) tills villkoret har rensats.
Vi rekommenderar att begäranden misslyckades på grund av begränsning görs på nytt med en exponentiell backoff enligt följande:
Anropsindex | Åtgärd för att ta emot 429 |
---|---|
1 | Vänta 1 sekund och försök igen |
2 | Vänta 2 sekunder och försök igen |
3 | Vänta 4 sekunder och försök igen |
4 | Vänta i 8 sekunder och försök igen |
4 | Vänta i 8 sekunder och försök igen |
5 | Vänta 16 sekunder och försök igen |
Resurs-ID:t för Azure-tjänster
Se Azure-tjänster som stöder Microsoft Entra-autentisering för en lista över resurser som stöder Microsoft Entra-ID och deras respektive resurs-ID:n.
Nästa steg
- Distribuera ett Service Fabric-program med hanterad identitet till ett hanterat kluster
- Distribuera ett Service Fabric-program med en systemtilldelad hanterad identitet till ett klassiskt kluster
- Distribuera ett Service Fabric-program med en användartilldelad hanterad identitet till ett klassiskt kluster
- Bevilja ett Service Fabric-program hanterad identitet åtkomst till Azure-resurser
- Utforska ett exempelprogram med hjälp av Hanterad Service Fabric-identitet