Closed danzuckerberg closed 2 weeks ago
I got success with configuring my spring boot api connected to azure app configuration via connection string.
I am running api’s on local dev boxes and in Azure Spring Apps Service as target.
You will have to do IAM on Azure side.
Your api’s must have identity and have a reader role on keyvault to allow azure app config service to read key vault secrets.
you can use spring profiles to use app configuration labels (tags) if you want your local dev setup matching the target.
I am away from my computer for a couple weeks to provide you some code snippets.
H2H
Best Regards, Victor
On Thu, Jul 18, 2024 at 10:26 AM danzuckerberg @.***> wrote:
Background
I am creating a basic demo application using Java Spring Boot. The application has three requirements:
- Retrieve App Configuration values (via App Configuration)
- Retrieve App Configuration Key Vault backed values (via App Configuration)
- Retrieve Key Vault Secrets (via Key Vault)
Problem
App Configuration does not respect my Azure CLI credentials for authentication, and fails to start the application. It claims it's using the DefaultAzureCredential, but it doesn't do the credentials chain properly. It gets to Managed Identity and then it attempts that over and over again until it aborts and the application fails to start.
AbstractAzureServiceClientBuilderFactory : No authentication credential configured for class ConfigurationClientBuilder. AbstractAzureServiceClientBuilderFactory : Will configure the default credential of type DefaultAzureCredential for class com.azure.data.appconfiguration.ConfigurationClientBuilder. [main] c.a.core.implementation.util.Providers : Using com.azure.core.http.netty.NettyAsyncHttpClientProvider as the default com.azure.core.http.HttpClientProvider. [main] c.a.c.h.n.implementation.NettyUtility : The following is Netty version information that was found on the classpath: 'io.netty:netty-common' version: 4.1.111.Final, 'io.netty:netty-handler' version: 4.1.111.Final, 'io.netty:netty-handler-proxy' version: 4.1.111.Final, 'io.netty:netty-buffer' version: 4.1.111.Final, 'io.netty:netty-codec' version: 4.1.111.Final, 'io.netty:netty-codec-http' version: 4.1.111.Final, 'io.netty:netty-codec-http2' version: 4.1.111.Final, 'io.netty:netty-transport-native-unix-common' version: 4.1.111.Final, 'io.netty:netty-transport-native-epoll' version: 4.1.111.Final, 'io.netty:netty-transport-native-kqueue' version: 4.1.111.Final. The version of azure-core-http-netty being used was built with Netty version 4.1.108.Final and Netty Tcnative version 2.0.65.Final. If your application runs without issue this message can be ignored, otherwise please align the Netty versions used in your application. For more information, see https://aka.ms/azsdk/java/dependency/troubleshoot. [main] .i.AppConfigurationReplicaClientsBuilder : Connecting to https://myconfigstore.azconfig.io using Azure System Assigned Identity. [ForkJoinPool.commonPool-worker-1] c.a.i.implementation.IdentityClient : ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: getsockopt. [ForkJoinPool.commonPool-worker-1] c.a.identity.ManagedIdentityCredential : Azure Identity => ERROR in getToken() call for scopes [https://myconfigstore.azconfig.io/.default]: Managed Identity authentication is not available.
Caused by: java.util.concurrent.ExecutionException: com.azure.identity.CredentialUnavailableException: ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: getsockopt. com.azure.core.http.policy.RetryPolicy : {"az.sdk.message":"Error resume.","exception":"Managed Identity authentication is not available.","tryCount":0}
c.a.i.implementation.IdentityClient : ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: getsockopt. ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: getsockopt. c.m.a.m.ConfidentialClientApplication : [Correlation ID: 838a2246-f41c-4f9f-97d5-30da7854e17a] Execution of class com.microsoft.aad.msal4j.AcquireTokenByClientCredentialSupplier failed: java.util.concurrent.ExecutionException: com.azure.identity.CredentialUnavailableException: ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: getsockopt. c.a.identity.ManagedIdentityCredential : Azure Identity => ERROR in getToken() call for scopes [https://myconfigstore.azconfig.io/.default]: Managed Identity authentication is not available. c.a.c.implementation.AccessTokenCache : {"az.sdk.message":"Failed to acquire a new access token.","exception":"Managed Identity authentication is not available."}
com.azure.core.http.policy.RetryPolicy : {"az.sdk.message":"Retry attempts have been exhausted.","exception":"Managed Identity authentication is not available.","tryCount":2}
.i.AppConfigurationPropertySourceLocator : Fail fast is set and there was an error reading configuration from Azure App Configuration store https://myconfigstore.azconfig.io. o.s.boot.SpringApplication : Application run failed
Key Vault does respect my Azure CLI credentials, and works out of the box.
c.azure.identity.ChainedTokenCredential : Azure Identity => Attempted credential EnvironmentCredential is unavailable. c.azure.identity.ChainedTokenCredential : Azure Identity => Attempted credential WorkloadIdentityCredential is unavailable. c.azure.identity.ChainedTokenCredential : Azure Identity => Attempted credential ManagedIdentityCredential is unavailable. c.azure.identity.ChainedTokenCredential : Azure Identity => Attempted credential SharedTokenCacheCredential is unavailable. c.azure.identity.ChainedTokenCredential : Azure Identity => Attempted credential IntelliJCredential is unavailable. c.a.i.implementation.IdentityClient : Azure CLI Authentication => A token response was received from Azure CLI, deserializing the response into an Access Token. com.azure.identity.AzureCliCredential : Azure Identity => getToken() result for scopes [https://vault.azure.net/.default]: SUCCESS c.azure.identity.ChainedTokenCredential : Azure Identity => Attempted credential AzureCliCredential returns a token c.a.c.implementation.AccessTokenCache : {"az.sdk.message":"Acquired a new access token."}
Project Details Relevant pom.xml
21 org.springframework.boot spring-boot-starter-parent 3.3.1 org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test com.azure.spring spring-cloud-azure-starter com.azure.spring spring-cloud-azure-appconfiguration-config com.azure.spring spring-cloud-azure-appconfiguration-config-web com.azure.spring spring-cloud-azure-starter-keyvault-secrets org.springframework.boot spring-boot-configuration-processor true com.azure.spring spring-cloud-azure-dependencies 5.14.0 pom import yml
spring: application: name: springboot-demo-app cloud: azure: appconfiguration: stores:
- endpoint: https://myconfigstore.azconfig.io selects:
- key-filter: /demo/ keyvault: secret: endpoint: https://mykeyvault.vault.azure.net
Alternatives I've Tried Add credential to yml
I figured if I explicitly skipped managed-identity, since that's where it's getting stuck, it would move on in the chain. However, I get the same errors and the app fails to start.
spring: application: name: springboot-demo-app cloud: azure: appconfiguration: stores:
- endpoint: https://myconfigstore.azconfig.io selects:
- key-filter: /demo/ credential: managed-identity-enabled: false
Connection String
I have also tried using the connection-string instead of endpoint. This works, but it only authenticates me to the app configuration store, and not the key vault. This is also not a great solution, I want to use DefaultAzureCredential everywhere I can so that the app runs both locally and when deployed to Azure.
spring: application: name: springboot-demo-app cloud: azure: appconfiguration: stores:
- connection-string: "Endpoint=https://myconfigstore.azconfig.io;Id=myid;Secret=mysecret" selects:
- key-filter: /demo/
Coding it myself
As a sanity check, I wrote my own simple implementation of this and it works perfectly fine. I'd just prefer to take advantage of the ConfigurationProperties() mapping all of the values for me (the whole point of using this library over the sdk)
private final String endpoint = "https://myconfigstore.azconfig.io"; DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
ConfigurationClient configurationClient = new ConfigurationClientBuilder().credential(credential).endpoint(endpoint) .buildClient();
public String getValue() { return configurationClient.getConfigurationSetting("/demo/myvalue", null).getValue(); }
public String getSecret() { return configurationClient.getConfigurationSetting("/demo/mysecret", null).getValue(); }
Final thoughts
Can someone please show me where I have this misconfigured? I'm just confused about why Key Vault works right out of the box, but App Configuration doesn't, especially because the logs indicate that App Configuration is using the DefaultAzureCredential.
If this is actually the intended behavior, can someone show me how to work around it so that Azure CLI is a valid authentication method?
Thanks!
— Reply to this email directly, view it on GitHub https://github.com/Azure/AppConfiguration/issues/941, or unsubscribe https://github.com/notifications/unsubscribe-auth/AF462ZKKSIU6LPXAYC2S2DLZM7NCLAVCNFSM6AAAAABLC5JU5CVHI2DSMVQWIX3LMV43ASLTON2WKOZSGQYTMNZSHEZDCMA . You are receiving this because you are subscribed to this thread.Message ID: @.***>
Thank you for the reply. As I mentioned in my post, I was able to get it working with the connection string but that method has several drawbacks.
This is not an issue with IAM on the Azure side, I have full access to both the App Configuration Store and the Key Vault (which is working as expected).
My issue is with this library not using the DefaultAzureCredentials chain for App Configuration.
@danzuckerberg, I don't think we document anywhere that we use a DefaultAzureCredential by default for providing an Endpoint. We only use ManagedIdentityCredential by default if nothing else was provided.
We support a few ways of providing a credential, which should also work with key vault. See: https://learn.microsoft.com/en-us/azure/azure-app-configuration/howto-convert-to-the-new-spring-boot?tabs=spring-boot-3#using-client-customizers
We should support any credential method here, https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/authentication#other-credential-types.
If I had a guess there might be a miss documentation of us doing the top part of that doc. We've always defaulted to a System Assigned Managed Identity, we have talked about changing but never have.
@mrm9084 thank you for the quick response. You're correct in that it's not explicitly documented. I assumed the App Configuration service would conveniently work the same way Key Vault does. I also observed the following in the logs when attempting to run the application:
AbstractAzureServiceClientBuilderFactory : No authentication credential configured for class ConfigurationClientBuilder.
AbstractAzureServiceClientBuilderFactory : Will configure the default credential of type DefaultAzureCredential for class com.azure.data.appconfiguration.ConfigurationClientBuilder.
I forgot to mention in my post that I also tried using a ConfigurationClientCustomerizer but it didn't have an effect.
@Configuration
public class ConfigurationClientCustomizerImpl implements ConfigurationClientCustomizer {
@Override
public void customize(ConfigurationClientBuilder builder, String endpoint) {
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
builder.credential(credential);
}
}
@danzuckerberg, I think there is a slight oversight when we tried making our App Config Spring library work better with the other Spring libraries. We use there builder to get a client, but we have different defaults and the SDK just overrides there default with ours.
We are working on a new Major version, we are discussing updating this then.
Something about your code doesn't look right. I don't think Spring is picking it up. Have you tried adding an @Component
to it. I usually just add it as an @Bean
to an existing @Configuration
it shouldn't really be an @Configuration
itself.
Hey @mrm9084 you're right, that configuration was not valid. That said, I tried two other options and they didn't work either. Clearly, I'm missing something
Option 1:
@Configuration
public class AzureCredentialsConfig {
@Bean
ConfigurationClientCustomizer configurationClientCustomizer() {
return new ConfigurationClientCustomizer() {
@Override
public void customize(ConfigurationClientBuilder builder, String endpoint) {
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
builder.credential(credential);
}
};
}
}
Option 2 (create an Impl class and wire it as a bean in a @Configuration)
public class ConfigurationClientCustomizerImpl implements ConfigurationClientCustomizer {
@Override
public void customize(ConfigurationClientBuilder builder, String endpoint) {
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
builder.credential(credential);
}
}
@Configuration
public class AzureCredentialsConfig {
@Bean
ConfigurationClientCustomizer configurationClientCustomizer() {
return new ConfigurationClientCustomizerImpl();
}
}
How do you have AzureCredentialsConfig
wired up? Do you have @AutoConfiguration
setup or a spring.factories
file pointing to it?
I can't find a sample that show it in action, but this Integration test is rather basic and uses it. https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring/spring-cloud-azure-integration-test-appconfiguration-config
Yeah, it's wired up. I use the SpringBootApplication annotation and have it scanning all of my base packages.
@SpringBootApplication(scanBasePackages = { "com.demo.demo" })
I can also hit my actuator/beans endpoint and see the bean in context (I think this is correct?)
Does it need to have a specific name?
"configurationClientCustomizer": {
"aliases": [],
"scope": "singleton",
"type": "com.demo.demo.config.ConfigurationClientCustomizerImpl",
"resource": "class path resource [com/demo/demo/config/AzureCredentialsConfig.class]",
"dependencies": [
"azureCredentialsConfig"
]
},
Does anything seem off compared to the linked sample? And did the error message change?
Also, are you using key vault references. If so, you would need to extend SecretClientCustomizer
too. As it would use that client.
It doesn't need any specific name, it picks up it by the interface class.
Yeah I have it set up like the test project. I renamed a couple of things but here's what I have compared to the test project.
AzureCredentialsCustomClient.java (similar to CustomClient.java)
public class AzureCredentialsCustomClient implements ConfigurationClientCustomizer, SecretClientCustomizer {
@Override
public void customize(ConfigurationClientBuilder builder, String endpoint) {
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
builder.credential(credential);
}
@Override
public void customize(SecretClientBuilder builder, String endpoint) {
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
builder.credential(credential);
}
}
AzureCredentialsConfig.java (similar to AppConfiguration.java)
@Configuration
public class AzureCredentialsConfig {
@Bean
AzureCredentialsCustomClient azureCredentials() {
return new AzureCredentialsCustomClient();
}
}
The bean is loaded into context:
"azureCredentials": {
"aliases": [],
"scope": "singleton",
"type": "com.demo.demo.config.AzureCredentialsCustomClient",
"resource": "class path resource [com/demo/demo/config/AzureCredentialsConfig.class]",
"dependencies": [
"azureCredentialsConfig"
]
},
Still stuck in a loop trying to get Managed Identity:
Caused by: com.azure.identity.CredentialUnavailableException: ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: getsockopt.
@mrm9084 any updates for this issue?
@danzuckerberg, have you had any luck on this? I have been unable to replicate after trying quite a few things. My only thought is that maybe that your AzureCredentialsCustomClient
Bean
is not being created till after we create our stuff. But that should only be the case if you deprioritized it in some way.
Also, to answer one of your above questions, we don't look for a bean by name, we do it by type we grab the bean of ConfigurationClientCustomizer
.
@mrm9084 still no luck on the workaround. I even threw it over the fence to a more seasoned Java developer to try in his project and it doesn't work for him either.
This is preventing us from running the application locally (blocker). The connection string approach only authenticates you to the app configuration store (and includes a secret so obviously we can't commit that), so if you have any key vault-backed values (which we do), it will fail because the connection string doesn't authenticate you to the key vault. Even if this did work, it would require us to maintain two sets of configurations (local/nonlocal), which is fine, but not ideal.
That said, when we deploy the application to an environment, it works as expected because our VM has a managed identity.
Back to my op, if this would just use the DefaultAzureCredential it would work out of the box, both locally and when deployed, and my developers can just use their own identities via az login
@danzuckerberg, I'm not sure what is going on. Here is a sample that shows how it should work. Can you let me know if it works for you and if anything is different than what you are trying to do?
@mrm9084 thank you, we will try it out. At first glance, I'm noticing that the only difference is the JAR that's being brought in.
We're using:
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-appconfiguration-config-web</artifactId>
</dependency>
But the sample is using:
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-appconfiguration-config</artifactId>
</dependency>
What is the difference/purpose for having distinct JARs here? Are we supposed to be using the starter
JARs? The code wrote and the code you wrote is identical in this case, so it might be as simple as swapping it out in our POM?
spring-cloud-azure-starter-appconfiguration-config
is just a starter library. It has no code, just contains spring-cloud-azure-appconfiguration-config-web
and spring-cloud-azure-feature-management-web
in a single pom file.
@mrm9084 thank you for that clarification. I cloned the sample repo and was able to get it working as expected.
The piece we were missing is the spring.factories
file that specifies the load order. The documentation doesn't mention that file anywhere, but I noticed it in both the integration test and sample app you sent us.
Once I plugged that into my own application, it started working as expected. For a sanity check, I removed that file from the sample application you provided, and it broke/stopped working, confirming the file is the missing piece.
I this can be closed then. A new issue can be created if any other issues come up.
Background
I am creating a basic demo application using Java Spring Boot. The application has three requirements:
Problem
App Configuration does not respect my Azure CLI credentials for authentication, and fails to start the application. It claims it's using the DefaultAzureCredential, but it doesn't do the credentials chain properly. It gets to Managed Identity and then it attempts that over and over again until it aborts and the application fails to start.
Key Vault does respect my Azure CLI credentials, and works out of the box.
Project Details
Relevant pom.xml
yml
Alternatives I've Tried
Add credential to yml
I figured if I explicitly skipped
managed-identity
, since that's where it's getting stuck, it would move on in the chain. However, I get the same errors and the app fails to start.Connection String
I have also tried using the
connection-string
instead ofendpoint
. This works, but it only authenticates me to the app configuration store, and not the key vault. This is also not a great solution, I want to use DefaultAzureCredential everywhere I can so that the app runs both locally and when deployed to Azure.Coding it myself
As a sanity check, I wrote my own simple implementation of this and it works perfectly fine. I'd just prefer to take advantage of the ConfigurationProperties() mapping all of the values for me (the whole point of using this library over the sdk)
Final thoughts
Can someone please show me where I have this misconfigured? I'm just confused about why Key Vault works right out of the box, but App Configuration doesn't, especially because the logs indicate that App Configuration is using the DefaultAzureCredential.
If this is actually the intended behavior, can someone show me how to work around it so that Azure CLI is a valid authentication method?
Thanks!