Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Os aplicativos do Service Fabric podem aproveitar identidades gerenciadas para acessar outros recursos do Azure que oferecem suporte à autenticação baseada em ID do Microsoft Entra. Um aplicativo pode obter um token de acesso que representa sua identidade, que pode ser atribuído pelo sistema ou pelo usuário, e usá-lo como um token de "portador" para autenticar-se em outro serviço - também conhecido como servidor de recursos protegidos. O token representa a identidade atribuída ao aplicativo Service Fabric e só será emitido para recursos do Azure (incluindo aplicativos SF) que compartilham essa identidade. Consulte a documentação de visão geral da identidade gerenciada para obter uma descrição detalhada das identidades gerenciadas, bem como a distinção entre identidades atribuídas pelo sistema e atribuídas pelo usuário. Chamaremos uma aplicação Service Fabric com identidade gerida ativada de aplicativo cliente ao longo deste artigo.
Veja um aplicativo de exemplo complementar que demonstra o uso de identidades gerenciadas de aplicativos do Service Fabric atribuídos pelo sistema e pelo usuário com Serviços Confiáveis e contêineres.
Important
A managed identity represents the association between an Azure resource and a service principal in the corresponding Microsoft Entra tenant associated with the subscription containing the resource. Como tal, no contexto do Service Fabric, as identidades geridas só têm suporte para aplicações implementadas como recursos do Azure.
Important
Antes de usar a identidade gerenciada de um aplicativo do Service Fabric, o aplicativo cliente deve ter acesso ao recurso protegido. Consulte a lista de serviços do Azure que suportam a autenticação Microsoft Entra para verificar se há suporte e, em seguida, a documentação do respetivo serviço para obter etapas específicas para conceder acesso de identidade a recursos de interesse.
Aproveite uma identidade gerenciada usando Azure.Identity
O SDK do Azure Identity agora dá suporte ao Service Fabric. Usar Azure.Identity facilita a escrita de código para usar identidades gerenciadas do aplicativo Service Fabric porque ele lida com a busca de tokens, tokens de cache e autenticação de servidor. Ao acessar a maioria dos recursos do Azure, o conceito de um token fica oculto.
O suporte do Service Fabric está disponível nas seguintes versões para estes idiomas:
- C# na versão 1.3.0. Veja um exemplo de C#.
- Python na versão 1.5.0. Veja um exemplo de Python.
- Java na versão 1.2.0.
Exemplo em C# de inicialização de credenciais e uso das credenciais para buscar um segredo do Cofre de Chaves do Azure:
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
}
}
}
}
Adquirindo um token de acesso usando a API REST
In clusters enabled for managed identity, the Service Fabric runtime exposes a localhost endpoint which applications can use to obtain access tokens. O ponto de extremidade está disponível em cada nó do cluster e é acessível a todas as entidades nesse nó. Authorized callers may obtain access tokens by calling this endpoint and presenting an authentication code; the code is generated by the Service Fabric runtime for each distinct service code package activation, and is bound to the lifetime of the process hosting that service code package.
Especificamente, o ambiente de um serviço do Service Fabric com identidade gerida ativada será configurado com as seguintes variáveis:
- 'IDENTITY_ENDPOINT': the localhost endpoint corresponding to service's managed identity
- 'IDENTITY_HEADER': a unique authentication code representing the service on the current node
- 'IDENTITY_SERVER_THUMBPRINT': Thumbprint of service fabric managed identity server
Important
O código do aplicativo deve considerar o valor da variável de ambiente 'IDENTITY_HEADER' como dados confidenciais - eles não devem ser registrados ou disseminados de outra forma. O código de autenticação não tem valor fora do nó local ou após o processo que hospeda o serviço ter terminado, mas representa a identidade do serviço Service Fabric e, portanto, deve ser tratado com as mesmas precauções que o próprio token de acesso.
Para obter um token, o cliente executa as seguintes etapas:
- forms a URI by concatenating the managed identity endpoint (IDENTITY_ENDPOINT value) with the API version and the resource (audience) required for the token
- cria uma solicitação GET http(s) para o URI especificado
- Adiciona a lógica de validação de certificado de servidor apropriada
- Adiciona o código de autenticação (valor IDENTITY_HEADER) como um cabeçalho à solicitação
- submete o pedido
Uma resposta bem-sucedida conterá uma carga JSON representando o token de acesso resultante, bem como metadados que o descrevem. Uma resposta com falha também incluirá uma explicação da falha. Veja abaixo detalhes adicionais sobre o tratamento de erros.
Os tokens de acesso serão armazenados em cache pelo Service Fabric em vários níveis (nó, cluster, serviço do provedor de recursos), portanto, uma resposta bem-sucedida não implica necessariamente que o token foi emitido diretamente em resposta à solicitação do aplicativo do usuário. Os tokens serão armazenados em cache por menos do que sua vida útil e, portanto, um aplicativo tem a garantia de receber um token válido. It is recommended that the application code caches itself any access tokens it acquires; the caching key should include (a derivation of) the audience.
Pedido de amostra:
GET 'https://localhost:2377/metadata/identity/oauth2/token?api-version=2019-07-01-preview&resource=https://vault.azure.net/' HTTP/1.1 Secret: aaaabbbb-0000-cccc-1111-dddd2222eeee
onde:
| Elemento | Descrição |
|---|---|
GET |
O verbo HTTP, indicando que pretende recuperar dados do endpoint. Nesse caso, um token de acesso OAuth. |
https://localhost:2377/metadata/identity/oauth2/token |
O endpoint de identidade gerido para aplicações do Service Fabric, fornecido por meio da variável de ambiente IDENTITY_ENDPOINT. |
api-version |
Um parâmetro de cadeia de caracteres de consulta, especificando a versão da API do Serviço de Token de Identidade Gerenciada; Atualmente, o único valor aceito é 2019-07-01-preview, e está sujeito a alterações. |
resource |
A query string parameter, indicating the App ID URI of the target resource. This will be reflected as the aud (audience) claim of the issued token. Este exemplo solicita um token para acessar o Azure Key Vault, cujo URI de ID do Aplicativo é https://vault.azure.net/. |
Secret |
Um campo de cabeçalho de solicitação HTTP, exigido pelo Serviço de Token de Identidade Gerida do Service Fabric para que os serviços do Service Fabric autentiquem o chamador. This value is provided by the SF runtime via IDENTITY_HEADER environment variable. |
Exemplo de resposta:
HTTP/1.1 200 OK
Content-Type: application/json
{
"token_type": "Bearer",
"access_token": "eyJ0eXAiO...",
"expires_on": 1565244611,
"resource": "https://vault.azure.net/"
}
onde:
| Elemento | Descrição |
|---|---|
token_type |
The type of token; in this case, a "Bearer" access token, which means the presenter ('bearer') of this token is the intended subject of the token. |
access_token |
O token de acesso solicitado. Ao chamar uma API REST segura, o token é incorporado no campo de cabeçalho da solicitação Authorization como um token "portador", permitindo que a API autentique o chamador. |
expires_on |
The timestamp of the expiration of the access token; represented as the number of seconds from "1970-01-01T0:0:0Z UTC" and corresponds to the token's exp claim. Neste caso, o token expira em 2019-08-08T06:10:11+00:00 (na RFC 3339) |
resource |
O recurso para o qual o token de acesso foi emitido, especificado através do parâmetro de consulta resource da solicitação, corresponde à declaração 'aud' do token. |
Adquirindo um token de acesso usando C#
O acima se torna, em 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
Acessando o Cofre da Chave a partir de um aplicativo do Service Fabric usando a Identidade Gerenciada
Este exemplo se baseia no acima para demonstrar o acesso a um segredo armazenado em um Cofre de Chaves usando a identidade gerenciada.
/// <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);
}
}
Tratamento de erros
O campo 'código de status' do cabeçalho da resposta HTTP indica o status de sucesso da solicitação; um status '200 OK' indica sucesso, e a resposta incluirá o token de acesso conforme descrito acima. Segue-se uma breve enumeração de possíveis respostas de erro.
| Código de estado | Motivo do erro | How To Handle |
|---|---|---|
| 404 Não encontrado. | Código de autenticação desconhecido ou o aplicativo não recebeu uma identidade gerenciada. | Retifique a configuração do aplicativo ou o código de aquisição de token. |
| 429 Demasiados pedidos. | Throttle limit reached, imposed by Microsoft Entra ID or SF. | Retry with Exponential Backoff. Consulte as orientações abaixo. |
| 4xx Erro na solicitação. | Um ou mais parâmetros de solicitação estavam incorretos. | Não tente novamente. Examine os detalhes do erro para obter mais informações. 4xx errors are design-time errors. |
| 5xx Error from service. | O subsistema de identidade gerenciado ou ID do Microsoft Entra retornou um erro transitório. | É seguro tentar novamente depois de um curto período de tempo. You may hit a throttling condition (429) upon retrying. |
Se ocorrer um erro, o corpo da resposta HTTP correspondente conterá um objeto JSON com os detalhes do erro:
| Elemento | Descrição |
|---|---|
| código | Código de erro. |
| correlationId | A correlation ID that can be used for debugging. |
| Mensagem | Descrição detalhada do erro. As descrições de erros podem ser alteradas a qualquer momento. Não dependa da mensagem de erro em si. |
Sample error:
{"error":{"correlationId":"aaaa0000-bb11-2222-33cc-444444dddddd","code":"SecretHeaderNotFound","message":"Secret is not found in the request headers."}}
A seguir está uma lista de erros típicos do Service Fabric específicos para identidades gerenciadas:
| Código | Mensagem | Descrição |
|---|---|---|
| SecretHeaderNotFound | O segredo não foi encontrado nos cabeçalhos de solicitação. | O código de autenticação não foi fornecido com a solicitação. |
| ManagedIdentityNotFound | Identidade gerenciada não encontrada para o host de aplicativo especificado. | O aplicativo não tem identidade ou o código de autenticação é desconhecido. |
| ArgumentNullOrEmpty | O parâmetro 'resource' não deve ser null ou vazio string. | O recurso (audiência) não foi fornecido na solicitação. |
| VersãoAPIInválida | A versão api '' não é suportada. A versão suportada é '2019-07-01-preview'. | Versão da API ausente ou sem suporte especificada no URI da solicitação. |
| Erro Interno do Servidor | Ocorreu um erro. | An error was encountered in the managed identity subsystem, possibly outside of the Service Fabric stack. A causa mais provável é um valor incorreto especificado para o recurso (verifique se há '/'?) |
Retry guidance
Normalmente, o único código de erro recuperável é 429 (Too Many Requests); os códigos de erro internos do servidor/5xx podem ser passíveis de repetição, embora a causa possa ser permanente.
Os limites de controlo aplicam-se ao número de chamadas feitas ao subsistema de identidade gerida - especificamente às dependências 'upstream' (o serviço de Identidade Gerida do Azure ou o serviço de tokens seguro). Service Fabric armazena tokens na cache em vários níveis do pipeline, mas, dada a natureza distribuída dos componentes envolvidos, o chamador pode receber respostas de limitação inconsistentes (ou seja, ser restringido num nó/instância de uma aplicação, mas não num nó diferente ao solicitar um token para a mesma identidade). Quando a condição de limitação é definida, as solicitações subsequentes da mesma aplicação podem falhar com o código de status HTTP 429 (Demasiadas solicitações) até que a condição seja removida.
It is recommended that requests failed due to throttling are retried with an exponential backoff, as follows:
| Índice de chamadas | Action on receiving 429 |
|---|---|
| 1 | Aguarde 1 segundo e tente novamente |
| 2 | Aguarde 2 segundos e tente novamente |
| 3 | Aguarde 4 segundos e tente novamente |
| 4 | Aguarde 8 segundos e tente novamente |
| 4 | Aguarde 8 segundos e tente novamente |
| 5 | Aguarde 16 segundos e tente novamente |
IDs de recursos para serviços do Azure
Consulte Serviços do Azure que dão suporte à autenticação do Microsoft Entra para obter uma lista de recursos que dão suporte à ID do Microsoft Entra e suas respetivas IDs de recurso.
Próximos passos
- Implantar um aplicativo do Service Fabric com Identidade Gerenciada em um cluster gerenciado
- Deploy a Service Fabric application with a system-assigned Managed Identity to a classic cluster
- Deploy a Service Fabric application with a user-assigned Managed Identity to a classic cluster
- Granting a Service Fabric application's Managed Identity access to Azure resources
- Explore um aplicativo de exemplo usando o Service Fabric Managed Identity