Azure / azure-sdk-for-java

This repository is for active development of the Azure SDK for Java. For consumers of the SDK we recommend visiting our public developer docs at https://docs.microsoft.com/java/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-java.
MIT License
2.35k stars 1.99k forks source link

[QUERY] Force refreshing access token to avoid Role assignment latency #34845

Open jerome13250 opened 1 year ago

jerome13250 commented 1 year ago

Query/Question We are trying to use the Azure Keyvault with option role-based access control (RBAC). However we can read in this documentation : Role assignments latency: at current expected performance, it will take up to 10 minutes (600 seconds) after role assignments is changed for role to be applied. For our goal this is excessively slow. In this other Azure documentation is given some kind of workaround for this issue : When you assign roles or remove role assignments, it can take up to 10 minutes for changes to take effect. If you're using the Azure portal, Azure PowerShell, or Azure CLI, you can force a refresh of your role assignment changes by signing out and signing in. If you're making role assignment changes with REST API calls, you can force a refresh by refreshing your access token.

So we have 2 questions: 1- We are using the azure sdk for java, since it uses the REST api in the background, is it also subject to this 10 minutes latency ? If yes please respond question 2. 2- We are using azure-identity version 1.8.2, we create a ClientSecretCredential. Then this class is used to create an AzureResourceManager that will allocate a role. Is it possible to force refreshing access token for role assignment to be executed immediately ? We have already tried to use the ClientSecretCredential.getTokenSync() method with scope = ( https://management.core.windows.net//.default ) to force the refresh, we can effectively get a new Token but, from what we can see, it does not refresh the token used by the AzureResourceManager...

Why is this not a Bug or a feature Request? We just want to know if it is possible to refresh an accessToken and how to do it to avoid the role assignment latency.

Setup (please complete the following information if applicable):

weidongxu-microsoft commented 1 year ago

@jerome13250

I kind of assume (1) also exist for SDK. Portal or CLI uses REST API.

For (2), the surest solution (but ugly) would be to create a new AzureResourceManager with a new ClientSecretCredential. (make sure to shared the HttpClient when create new AzureResourceManager)

@XiaofeiCao for easier solution. It may not be simple, as there is a AccessTokenCache in the middle of azure-resourcemanager and azure-identity (and it is private access to azure-resourcemanager).

jerome13250 commented 1 year ago

@weidongxu-microsoft thanks for your answer, if we use the surest solution and create a new instance of ClientSecretCredential + AzureResourceManager, in this case we have two ClientSecretCredential + AzureResourceManager that have no link together. Does this mean that the REST API will decide in this case to execute immediate role assignment when a new token is requested for the same user even if does not refresh an existing token ?

So let's consider this scenario : We create a ClientSecretCredential + AzureResourceManager. We create a RoleAssignment. If we use the method ClientSecretCredential.getTokenSync() to get a new Token (with scope = https://management.core.windows.net//.default ). Can you tell me if this triggers on the REST API side the immediate role assignment execution ? (even if the resulting token is not used by the AzureResourceManager since it uses its own private cached token).

XiaofeiCao commented 1 year ago

Hi @jerome13250 , I'm afraid not. In order to refresh token(I assume you mean get a new token?), you could try this:

// initialize token client
ConfidentialClientApplication cca = ConfidentialClientApplication
    .builder(
        clientId, ClientCredentialFactory.createFromSecret(yourClientSecret)
    )
    .authority(removeTrailingSlash(AzureEnvironment.AZURE.getActiveDirectoryEndpoint()) + "/" + tenantId)
    .build();

// get new token
IAuthenticationResult authenticationResult = cca.acquireToken(
    ClientCredentialParameters
        .builder(Set.of("https://management.core.windows.net//.default")) // scope
        .skipCache(true) // this is required to force get new token, instead of reading from cache
        .build())
    .get();

// removeTrailingSlash implementation
private String removeTrailingSlash(String url) {
    Pattern TRAILING_FORWARD_SLASHES = Pattern.compile("/+$");
    return TRAILING_FORWARD_SLASHES.matcher(url).replaceAll("");
}

This above code should guarantee that you'll get a new access token each time, instead of any caching from SDK side.

The reason we have to do this is that ClientSecretCredential is built upon msal library, and they have their own cache machenisms as well... See here.

However, as you may already know, this will not refresh any access token held by any AzureResourceManager instances. This means that all existing AzureResourceManager's are still using their own cached token(though valid).

I've not tested whether role assignment will be synced once new token is requested. But if you are looking for ways to acquire token manually, I'm afraid it's the current best way you can get.

XiaofeiCao commented 1 year ago

@jerome13250 Weidong and I had an offline discussion, seems I got a misunderstanding of your question.. If you create a new AzureResourceManager + ClientSecretCredential, then ClientSecretCredential.getTokenSync() will guarantee that you'll get a new access token.

To clarify, do you want to use the new access token, or are you wondering if existing tokens(not refreshed) can experience the new role assignment?

jerome13250 commented 1 year ago

@XiaofeiCao thank you for the follow-up of this issue. You understood correctly my question, i was hoping to create a unique [ AzureResourceManager + ClientSecretCredential ] and then call ClientSecretCredential.getTokenSync() everytime i need to force the system to execute Role Assignment. But as you mentioned there's also a cache mechanism in ClientSecretCredential, the first call to getTokenSync() creates a new token and unfortunately the second (and all following) returns the same cached token created in first call.

Sorry for my confusing description.

To answer your question : I do not need to use the new access token, all i need is to avoid Role assignment latency. I think that existing token(not refreshed) is still valid, as you mentioned it in your previous response "This means that all existing AzureResourceManager's are still using their own cached token(though valid)."

Ok so i propose a new Scenario: 1- Create at program startup: first ClientSecretCredential : ClientSecretCredential_1 2- Create at program startup: unique AzureResourceManager : AzureResourceManager_unique (using ClientSecretCredential_1) 3- Do some RoleAssignment using AzureResourceManager_unique => i need to avoid role assignment latency : a- Create a second ClientSecretCredential : ClientSecretCredential_2 b- Execute ClientSecretCredential_2.getTokenSync() => No cache since this was never called for this object => get a new Token from API => this should force role assignment execution. 4- Do some other stuff... 5- Do some RoleAssignment using AzureResourceManager_unique => i need to avoid role assignment latency : a- Create a third ClientSecretCredential : ClientSecretCredential_3 b- Execute ClientSecretCredential_3.getTokenSync() => No cache since this was never called for this object => get a new Token from API => this should force role assignment execution.

This scenario is the same as proposed by weidongxu-microsoft, but i donnot create AzureResourceManager everytime. A new ClientSecretCredential is sufficient to get a Token. Please tell me if you see some flaws in it ?

XiaofeiCao commented 1 year ago

Thanks @jerome13250 for your detailed explanation. I believe now I understand your situation.

A new ClientSecretCredential is effectively the same as what Weidong proposed, with that I don't see a flaw. Make sure to share the same HttpClient between newly created ClientSecretCredentials, to avoid creating new connection pools each time:

ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
    .httpClient(httpClient)
    .xxx
    .build()

Though personally I suspect that the role assignment refresh only applies to new tokens, not existing ones. The sign-out sign-in approach for Portal, CLI or PowerShell is to get new tokens to use after all..