Answered by:
Assigning Azure Key Vault access policies to Azure Active Directory security groups containing Managed Service Identities doesn't work

Question
-
I ran into the 16 entry limit on the number of entries allowed in a Key Vault access control policy so followed the instructions on this page to create a new security group, and added my applications to the security group. I have a mixture of manually created service principals (using
New-AzureRmADApplication
andNew-AzureRmADServicePrincipal
and some created by enabling Managed Service Identity in Azure Web Apps. The former were able to access Key Vault successfully, the latter failed with "Access denied" errors. I restarted the Web Apps multiple times and checked that the security group contained the expected service principals, and that the access policies (–PermissionsToSecrets get
) were correct.
The group type is "Security" and I can see through the Azure Portal that there are MSI service principals in it. But I get access denied when accessing Key Vault.
It works fine when the same MSI is used but not in a group. But then I start running into the 16 entry limit.I've found a similar sounding, but old, issue on Stacknbsp;https://stackoverflow.com/questions/35589043/azure-key-vault-access-policy-doesnt-work-for-groups
I've raised this on GitHub but have been asked to re-raise it here. See: https://github.com/MicrosoftDocs/azure-docs/issues/9729#issuecomment-394765061
Thanks!
- Edited by tjrobinson Friday, June 8, 2018 3:58 PM
Answers
-
I wrote a test application to try this out and I've worked out what the problem was. The access token gets cached (I haven't worked out how long for, or exactly where). So although I've put the Service Principal into a security group, it doesn't get included in the access token until the previous one has expired.
Access token before the Service Principal is added to the security group:
{typ: "JWT",alg: "RS256",x5t: "redacted",kid: "redacted"}.{aud: "https://vault.azure.net",iss: "https://sts.windows.net/redacted/",iat: 1528729494,nbf: 1528729494,exp: 1528758594,aio: "redacted",appid: "redacted",appidacr: "2",e_exp: 288000,idp: "https://sts.windows.net/redacted/",oid: "redacted",sub: "redacted",tid: "redacted",uti: "redacted",ver: "1.0"}.[signature]
Access token after the Service Principal is added to the security group, and after the previous token has expired (note the "groups" property):
{typ: "JWT",alg: "RS256",x5t: "redacted",kid: "redacted"}.{aud: "https://vault.azure.net",iss: "https://sts.windows.net/redacted/",iat: 1528789058,nbf: 1528789058,exp: 1528818158,aio: "redacted",appid: "redacted",appidacr: "2",e_exp: 288000,groups: ["redacted"],idp: "https://sts.windows.net/redacted/",oid: "redacted",sub: "redacted",tid: "redacted",uti: "redacted",ver: "1.0"}.[signature]For reference, this is the code I used to test this (stick it in an MVC controller):
public async Task<ActionResult> Index() { try { ViewBag.MSI_SECRET = Environment.GetEnvironmentVariable("MSI_SECRET"); ViewBag.MSI_ENDPOINT = Environment.GetEnvironmentVariable("MSI_ENDPOINT"); using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Add("Secret", Environment.GetEnvironmentVariable("MSI_SECRET")); var msiAccessTokenResponse = await httpClient.GetAsync( $"{Environment.GetEnvironmentVariable("MSI_ENDPOINT")}/?resource=https://vault.azure.net&api-version=2017-09-01"); ViewBag.MsiAccessTokenResponse = msiAccessTokenResponse; ViewBag.MsiAccessTokenContent = await msiAccessTokenResponse.Content.ReadAsStringAsync(); } var azureServiceTokenProvider = new AzureServiceTokenProvider(); ViewBag.AccessTokenFromAzureServiceTokenProvider = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net"); var keyVaultTokenCallback = azureServiceTokenProvider.KeyVaultTokenCallback; var authenticationCallback = new KeyVaultClient.AuthenticationCallback(keyVaultTokenCallback); var keyVaultClient = new KeyVaultClient(authenticationCallback); ViewBag.Principal = azureServiceTokenProvider.PrincipalUsed != null ? $"{azureServiceTokenProvider.PrincipalUsed}" : string.Empty; var secret = await keyVaultClient.GetSecretAsync("https://myvaultname.vault.azure.net/secrets/TestSecret") .ConfigureAwait(false); ViewBag.Secret = $"Secret: {secret.Value}"; } catch (Exception ex) { ViewBag.Error = $"Something went wrong: {ex.Message}"; } return View(); }
Is there any way of forcing AzureServiceTokenProvider to get a new token and not a cached one?
- Proposed as answer by MohitGarg_MSFTModerator Wednesday, June 27, 2018 9:45 PM
- Marked as answer by tjrobinson Wednesday, July 11, 2018 9:00 AM
All replies
-
tjrobinson,
Could you help us debug this by sending a security token from inside the web app, so we can verify the group claims are in the token? Please send an email to azurekeyvault@microsoft.com.
Thanks
Amit- Edited by Amit Bapat [MSFT]Microsoft employee, Owner Friday, June 8, 2018 4:05 PM
-
-
-
I wrote a test application to try this out and I've worked out what the problem was. The access token gets cached (I haven't worked out how long for, or exactly where). So although I've put the Service Principal into a security group, it doesn't get included in the access token until the previous one has expired.
Access token before the Service Principal is added to the security group:
{typ: "JWT",alg: "RS256",x5t: "redacted",kid: "redacted"}.{aud: "https://vault.azure.net",iss: "https://sts.windows.net/redacted/",iat: 1528729494,nbf: 1528729494,exp: 1528758594,aio: "redacted",appid: "redacted",appidacr: "2",e_exp: 288000,idp: "https://sts.windows.net/redacted/",oid: "redacted",sub: "redacted",tid: "redacted",uti: "redacted",ver: "1.0"}.[signature]
Access token after the Service Principal is added to the security group, and after the previous token has expired (note the "groups" property):
{typ: "JWT",alg: "RS256",x5t: "redacted",kid: "redacted"}.{aud: "https://vault.azure.net",iss: "https://sts.windows.net/redacted/",iat: 1528789058,nbf: 1528789058,exp: 1528818158,aio: "redacted",appid: "redacted",appidacr: "2",e_exp: 288000,groups: ["redacted"],idp: "https://sts.windows.net/redacted/",oid: "redacted",sub: "redacted",tid: "redacted",uti: "redacted",ver: "1.0"}.[signature]For reference, this is the code I used to test this (stick it in an MVC controller):
public async Task<ActionResult> Index() { try { ViewBag.MSI_SECRET = Environment.GetEnvironmentVariable("MSI_SECRET"); ViewBag.MSI_ENDPOINT = Environment.GetEnvironmentVariable("MSI_ENDPOINT"); using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Add("Secret", Environment.GetEnvironmentVariable("MSI_SECRET")); var msiAccessTokenResponse = await httpClient.GetAsync( $"{Environment.GetEnvironmentVariable("MSI_ENDPOINT")}/?resource=https://vault.azure.net&api-version=2017-09-01"); ViewBag.MsiAccessTokenResponse = msiAccessTokenResponse; ViewBag.MsiAccessTokenContent = await msiAccessTokenResponse.Content.ReadAsStringAsync(); } var azureServiceTokenProvider = new AzureServiceTokenProvider(); ViewBag.AccessTokenFromAzureServiceTokenProvider = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net"); var keyVaultTokenCallback = azureServiceTokenProvider.KeyVaultTokenCallback; var authenticationCallback = new KeyVaultClient.AuthenticationCallback(keyVaultTokenCallback); var keyVaultClient = new KeyVaultClient(authenticationCallback); ViewBag.Principal = azureServiceTokenProvider.PrincipalUsed != null ? $"{azureServiceTokenProvider.PrincipalUsed}" : string.Empty; var secret = await keyVaultClient.GetSecretAsync("https://myvaultname.vault.azure.net/secrets/TestSecret") .ConfigureAwait(false); ViewBag.Secret = $"Secret: {secret.Value}"; } catch (Exception ex) { ViewBag.Error = $"Something went wrong: {ex.Message}"; } return View(); }
Is there any way of forcing AzureServiceTokenProvider to get a new token and not a cached one?
- Proposed as answer by MohitGarg_MSFTModerator Wednesday, June 27, 2018 9:45 PM
- Marked as answer by tjrobinson Wednesday, July 11, 2018 9:00 AM
-
-
Hi @tjrobinson,
I want to ask if you were able to overcome this issue. Currently, we are facing exactly the same problem as you.
Our scenario is:
- Provide infrastructure in Azure via scripts with MSI enabled for all Web App Services
- Create AD Group and add all Web App Services into the group
- Add the created AD Group into the Key Vault's Access Policies
- Deploy applications into all Web App Services
- Run Integration/UI/Performance/Security Tests and collect results
- Delete the infrastructure
As you can imagine, when running specific tests, all our Web App Services are getting Forbidden response from Key Vault. Of course, everything starts working on its own in some time (to be precise restart of Web App Service is needed)
-
That's odd, I'd expect that to work as because your AD Group is new, it wouldn't be cached? Unless the name is the same each time? Are you using a different name for it each time? That may help.
We've not resolved this yet, we just create/update the AD Groups well in advance of needing them or add the individual identities to the Key Vault access policy directly, just for a short time until the cache duration expires (still not sure how long this is). -
-