none
Assigning Azure Key Vault access policies to Azure Active Directory security groups containing Managed Service Identities doesn't work RRS feed

  • 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 and New-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
    Friday, June 8, 2018 3:56 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?

    Tuesday, June 12, 2018 8:05 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


    Friday, June 8, 2018 4:02 PM
    Owner
  • Hi Amit,

    Could you tell me how I would do that, and how I would send it to you securely?

    Thanks,

    Tom
    Friday, June 8, 2018 4:05 PM
  • Tom,

    Token expires in one hour. So wait for it to expire and then send it.

    Thanks
    Amit

    Friday, June 8, 2018 4:22 PM
    Owner
  • 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?

    Tuesday, June 12, 2018 8:05 AM
  • See follow-up question, regarding caching: https://social.msdn.microsoft.com/Forums/azure/en-US/83a1db1d-79bb-4132-97a6-e68d213f3b25/managed-service-identity-caching-of-access-tokens-by-msiendpoint?forum=AzureKeyVault
    Friday, July 13, 2018 10:09 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:

    1. Provide infrastructure in Azure via scripts with MSI enabled for all Web App Services
    2. Create AD Group and add all Web App Services into the group
    3. Add the created AD Group into the Key Vault's Access Policies 
    4. Deploy applications into all Web App Services
    5. Run Integration/UI/Performance/Security Tests and collect results
    6. 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)

    Saturday, December 1, 2018 2:02 PM
  • 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).
    Monday, December 3, 2018 9:05 AM
  • Yes, we are always creating a new AD Group with a new unique name and deleting if after execution of tests.
    • Edited by strho Monday, December 3, 2018 3:19 PM
    Monday, December 3, 2018 2:41 PM
  • We have the same problem.

    Security Groups of Managed Identities do not work.

    Workaround, use Security Group of Active Directory's Registered Apps.

    It is quite clear that the Documentation is not accurate and it should mention clearly this limitation.

    Friday, August 16, 2019 4:12 PM