Skip to content

Commit e0e11b2

Browse files
committed
SF Managed Identity doc and sample updates/fixes
1 parent dd700b9 commit e0e11b2

4 files changed

+85
-35
lines changed

articles/service-fabric/concepts-managed-identity.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ The list of supported scenarios for the preview release is as follows:
5858

5959
The following scenarios are not supported or not recommended; note these actions may not be blocked, but can lead to outages in your applications:
6060

61-
- Remove or change the identities assigned to an application;if you must make changes, submit separate deployments to first add a new identity assignment, and then to remove a previously assigned one. Removal of an identity from an existing application can have undesirable effects, including leaving your application in a state which is not upgradeable. It is safe to delete the application altogether if the removal of an identity is necessary; note this will delete the system-assigned identity (if so defined) associated with the application, and will remove any associations with the user-assigned identities assigned to the application.
61+
- Remove or change the identities assigned to an application; if you must make changes, submit separate deployments to first add a new identity assignment, and then to remove a previously assigned one. Removal of an identity from an existing application can have undesirable effects, including leaving your application in a state which is not upgradeable. It is safe to delete the application altogether if the removal of an identity is necessary; note this will delete the system-assigned identity (if so defined) associated with the application, and will remove any associations with the user-assigned identities assigned to the application.
6262

63-
- It is not recommended to mix system-assigned and user-assigned identities in the same application.
6463
>
6564
> [!NOTE]
6665
>

articles/service-fabric/how-to-deploy-service-fabric-application-system-assigned-managed-identity.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,9 @@ This property declares (to Azure Resource Manager, and the Managed Identity and
8989
This is the equivalent mapping of an identity to a service as described above, but from the perspective of the service definition. The identity is referenced here by its friendly name (`WebAdmin`), as declared in the application manifest.
9090

9191
## Next Steps
92-
9392
* Review [managed identity support](./concepts-managed-identity.md) in Azure Service Fabric
94-
9593
* [Deploy a new](./configure-new-azure-service-fabric-enable-managed-identity.md) Azure Service Fabric cluster with managed identity support
96-
9794
* [Enable managed identity](./configure-existing-cluster-enable-managed-identity-token-service.md) in an existing Azure Service Fabric cluster
98-
9995
* Leverage a Service Fabric application's [managed identity from source code](./how-to-managed-identity-service-fabric-app-code.md)
100-
10196
* [Deploy an Azure Service Fabric application with a user-assigned managed identity](./how-to-deploy-service-fabric-application-user-assigned-managed-identity.md)
102-
10397
* [Grant an Azure Service Fabric application access to other Azure resources](./how-to-grant-access-other-resources.md)

articles/service-fabric/how-to-grant-access-other-resources.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,5 @@ The following example illustrates granting access to a vault via a template depl
6565
For more details, please see [Vaults - Update Access Policy](https://docs.microsoft.com/rest/api/keyvault/vaults/updateaccesspolicy).
6666

6767
## Next steps
68-
6968
* [Deploy an Azure Service Fabric application with a system-assigned managed identity](./how-to-deploy-service-fabric-application-system-assigned-managed-identity.md)
70-
7169
* [Deploy an Azure Service Fabric application with a user-assigned managed identity](./how-to-deploy-service-fabric-application-user-assigned-managed-identity.md)

articles/service-fabric/how-to-managed-identity-service-fabric-app-code.md

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,26 @@ namespace Azure.ServiceFabric.ManagedIdentity.Samples
9494
using System.Threading;
9595
using System.Threading.Tasks;
9696
using System.Web;
97+
using Newtonsoft.Json;
98+
99+
/// <summary>
100+
/// Type representing the response of the SF Managed Identity endpoint for token acquisition requests.
101+
/// </summary>
102+
[JsonObject]
103+
public sealed class ManagedIdentityTokenResponse
104+
{
105+
[JsonProperty(Required = Required.Always, PropertyName = "token_type")]
106+
public string TokenType { get; set; }
107+
108+
[JsonProperty(Required = Required.Always, PropertyName = "access_token")]
109+
public string AccessToken { get; set; }
110+
111+
[JsonProperty(PropertyName = "expires_on")]
112+
public string ExpiresOn { get; set; }
113+
114+
[JsonProperty(PropertyName = "resource")]
115+
public string Resource { get; set; }
116+
}
97117

98118
/// <summary>
99119
/// Sample class demonstrating access token acquisition using Managed Identity.
@@ -124,10 +144,12 @@ namespace Azure.ServiceFabric.ManagedIdentity.Samples
124144

125145
response.EnsureSuccessStatusCode();
126146

127-
var accessToken = await response.Content.ReadAsStringAsync()
147+
var tokenResponseString = await response.Content.ReadAsStringAsync()
128148
.ConfigureAwait(false);
129149

130-
return accessToken.Trim('"');
150+
var tokenResponseObject = JsonConvert.DeserializeObject<ManagedIdentityTokenResponse>(tokenResponseString);
151+
152+
return tokenResponseObject.AccessToken;
131153
}
132154
catch (Exception ex)
133155
{
@@ -157,10 +179,10 @@ This sample builds on the above to demonstrate accessing a secret stored in a Ke
157179
// initialize a KeyVault client with a managed identity-based authentication callback
158180
var kvClient = new Microsoft.Azure.KeyVault.KeyVaultClient(new Microsoft.Azure.KeyVault.KeyVaultClient.AuthenticationCallback((a, r, s) => { return AuthenticationCallbackAsync(a, r, s); }));
159181

160-
Console.WriteLine($"\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}");
182+
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}");
161183
string response = String.Empty;
162184

163-
Console.WriteLine("\n== Probing secret...");
185+
Log(LogLevel.Info, "\n== {DateTime.UtcNow.ToString()}: Probing secret...");
164186
try
165187
{
166188
var secretResponse = await kvClient.GetSecretWithHttpMessagesAsync(vault, secret, version)
@@ -169,12 +191,11 @@ This sample builds on the above to demonstrate accessing a secret stored in a Ke
169191
if (secretResponse.Response.IsSuccessStatusCode)
170192
{
171193
// use the secret: secretValue.Body.Value;
172-
var secretMetadata = secretResponse.Body.ToString();
173-
Console.WriteLine($"Successfully probed secret '{secret}' in vault '{vault}': {PrintSecretBundleMetadata(secretResponse.Body)}");
194+
response = String.Format($"Successfully probed secret '{secret}' in vault '{vault}': {PrintSecretBundleMetadata(secretResponse.Body)}");
174195
}
175196
else
176197
{
177-
Console.WriteLine($"Non-critical error encountered retrieving secret '{secret}' in vault '{vault}': {secretResponse.Response.ReasonPhrase} ({secretResponse.Response.StatusCode})");
198+
response = String.Format($"Non-critical error encountered retrieving secret '{secret}' in vault '{vault}': {secretResponse.Response.ReasonPhrase} ({secretResponse.Response.StatusCode})");
178199
}
179200
}
180201
catch (Microsoft.Rest.ValidationException ve)
@@ -191,46 +212,68 @@ This sample builds on the above to demonstrate accessing a secret stored in a Ke
191212
response = String.Format($"encountered exception 0x{ex.HResult.ToString("X")} trying to access '{secret}' in vault '{vault}': {ex.Message}");
192213
}
193214

194-
Console.WriteLine(response);
215+
Log(LogLevel.Info, response);
195216

196217
return response;
197218
}
198219

199220
/// <summary>
200221
/// KV authentication callback, using the application's managed identity.
201222
/// </summary>
202-
/// <param name="authority"></param>
203-
/// <param name="resource"></param>
204-
/// <param name="scope"></param>
223+
/// <param name="authority">The expected issuer of the access token, from the KV authorization challenge.</param>
224+
/// <param name="resource">The expected audience of the access token, from the KV authorization challenge.</param>
225+
/// <param name="scope">The expected scope of the access token; not currently used.</param>
205226
/// <returns>Access token</returns>
206227
public async Task<string> AuthenticationCallbackAsync(string authority, string resource, string scope)
207228
{
208-
if (config.DoVerboseLogging)
209-
Console.WriteLine($"authentication callback invoked with: auth: {authority}, resource: {resource}, scope: {scope}");
210-
211-
var requestUri = $"{config.ManagedIdentityEndpoint}?api-version={config.ManagedIdentityApiVersion}&resource={HttpUtility.UrlEncode(resource)}";
212-
if (config.DoVerboseLogging)
213-
Console.WriteLine($"request uri: {requestUri}");
229+
Log(LogLevel.Verbose, $"authentication callback invoked with: auth: {authority}, resource: {resource}, scope: {scope}");
230+
var encodedResource = HttpUtility.UrlEncode(resource);
231+
232+
// This sample does not illustrate the caching of the access token, which the user application is expected to do.
233+
// For a given service, the caching key should be the (encoded) resource uri. The token should be cached for a period
234+
// of time at most equal to its remaining validity. The 'expires_on' field of the token response object represents
235+
// the number of seconds from Unix time when the token will expire. You may cache the token if it will be valid for at
236+
// least another short interval (1-10s). If its expiration will occur shortly, don't cache but still return it to the
237+
// caller. The MI endpoint will not return an expired token.
238+
// Sample caching code:
239+
//
240+
// ManagedIdentityTokenResponse tokenResponse;
241+
// if (responseCache.TryGetCachedItem(encodedResource, out tokenResponse))
242+
// {
243+
// Log(LogLevel.Verbose, $"cache hit for key '{encodedResource}'");
244+
//
245+
// return tokenResponse.AccessToken;
246+
// }
247+
//
248+
// Log(LogLevel.Verbose, $"cache miss for key '{encodedResource}'");
249+
//
250+
// where the response cache is left as an exercise for the reader. MemoryCache is a good option, albeit not yet available on .net core.
251+
252+
var requestUri = $"{config.ManagedIdentityEndpoint}?api-version={config.ManagedIdentityApiVersion}&resource={encodedResource}";
253+
Log(LogLevel.Verbose, $"request uri: {requestUri}");
214254

215255
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
216256
requestMessage.Headers.Add(config.ManagedIdentityAuthenticationHeader, config.ManagedIdentityAuthenticationCode);
217-
if (config.DoVerboseLogging)
218-
Console.WriteLine($"added header '{config.ManagedIdentityAuthenticationHeader}': '{config.ManagedIdentityAuthenticationCode}'");
257+
Log(LogLevel.Verbose, $"added header '{config.ManagedIdentityAuthenticationHeader}': '{config.ManagedIdentityAuthenticationCode}'");
219258

220259
var response = await httpClient.SendAsync(requestMessage)
221260
.ConfigureAwait(false);
222-
if (config.DoVerboseLogging)
223-
Console.WriteLine($"response status: success: {response.IsSuccessStatusCode}, status: {response.StatusCode}");
261+
Log(LogLevel.Verbose, $"response status: success: {response.IsSuccessStatusCode}, status: {response.StatusCode}");
224262

225263
response.EnsureSuccessStatusCode();
226264

227-
var accessToken = await response.Content.ReadAsStringAsync()
265+
var tokenResponseString = await response.Content.ReadAsStringAsync()
228266
.ConfigureAwait(false);
229267

230-
if (config.DoVerboseLogging)
231-
Console.WriteLine("returning access code..");
268+
var tokenResponse = JsonConvert.DeserializeObject<ManagedIdentityTokenResponse>(tokenResponseString);
269+
Log(LogLevel.Verbose, "deserialized token response; returning access code..");
232270

233-
return accessToken.Trim('"');
271+
// Sample caching code (continuation):
272+
// var expiration = DateTimeOffset.FromUnixTimeSeconds(Int32.Parse(tokenResponse.ExpiresOn));
273+
// if (expiration > DateTimeOffset.UtcNow.AddSeconds(5.0))
274+
// responseCache.AddOrUpdate(encodedResource, tokenResponse, expiration);
275+
276+
return tokenResponse.AccessToken;
234277
}
235278

236279
private string PrintSecretBundleMetadata(SecretBundle bundle)
@@ -250,6 +293,22 @@ This sample builds on the above to demonstrate accessing a secret stored in a Ke
250293

251294
return strBuilder.ToString();
252295
}
296+
297+
private enum LogLevel
298+
{
299+
Info,
300+
Verbose
301+
};
302+
303+
private void Log(LogLevel level, string message)
304+
{
305+
if (level != LogLevel.Verbose
306+
|| config.DoVerboseLogging)
307+
{
308+
Console.WriteLine(message);
309+
}
310+
}
311+
253312
```
254313

255314
## Error handling

0 commit comments

Comments
 (0)