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.3k stars 1.96k forks source link

[QUERY] Correct configuration for spring-cloud-azure-stream-binder-servicebus using credentials as service principal. #37010

Open fredysierra opened 11 months ago

fredysierra commented 11 months ago

Query/Question We have a simple spring app that uses Azure Service Bus with Spring Cloud Stream and spring-cloud-azure-stream-binder-servicebus using credentials as service principal as described here. Our main goal is to disable SAS key authentication for a Service Bus namespace and allow only Azure Active Directory authentication.

I added the following configuration:

spring:
  cloud:
    azure:
      credential:
        client-id: ${AZURE_CLIENT_ID}
        client-secret: ${AZURE_CLIENT_SECRET}
      profile:
        tenant-id: ${AZURE_TENANT_ID}
        subscription-id: ${AZURE_SUBSCRIPTION_ID}
      servicebus:
        enabled: true
        entity-type: topic
        resource:
          resource-group: ${AZURE_RESOURCE_GROUP}
        namespace: ${SERVICEBUS_NAMESPACE}
    stream:
      function.definition: consume
      bindings:
        consume-in-0.destination: topic0
        consume-in-0.group: sub0
        hello-out-0.destination: topic0
      servicebus:
        bindings:
          consume-in-0.consumer.auto-complete: false
          hello-out-0.producer.entity-type: topic
      default-binder: servicebus

With this configuration, it is possible to see that the topic topic0 and subscription sub0 have been automatically created when the application starts.

2023-09-29 12:44:29.897  INFO 27842 --- [           main] zureServiceBusMessagingAutoConfiguration : Service Bus connection string is set from com.azure.spring.cloud.resourcemanager.implementation.connectionstring.ServiceBusArmConnectionStringProvider now.
2023-09-29 12:44:30.157  INFO 27842 --- [           main] o.s.c.s.m.DirectWithAttributesChannel    : Channel 'application.consume-in-0' has 1 subscriber(s).
2023-09-29 12:44:30.225  INFO 27842 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 4 endpoint(s) beneath base path '/actuator'
2023-09-29 12:44:30.327  INFO 27842 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel errorChannel
2023-09-29 12:44:30.330  INFO 27842 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel consume-in-0
2023-09-29 12:44:30.335  INFO 27842 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel nullChannel
2023-09-29 12:44:30.338  INFO 27842 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler _org.springframework.integration.errorLogger
2023-09-29 12:44:30.347  INFO 27842 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2023-09-29 12:44:30.347  INFO 27842 --- [           main] o.s.i.channel.PublishSubscribeChannel    : Channel 'application.errorChannel' has 1 subscriber(s).
2023-09-29 12:44:30.347  INFO 27842 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started bean '_org.springframework.integration.errorLogger'
2023-09-29 12:44:30.609  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusSubscription with name 'sub0' ...
2023-09-29 12:44:30.610  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching Topic with name 'topic0' ...
2023-09-29 12:44:30.610  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusNamespace with name 'testrnddesb' ...
2023-09-29 12:44:31.021  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusNamespace with name 'testrnddesb' finished in 0 seconds
2023-09-29 12:44:32.011  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching Topic with name 'topic0' finished in 1 seconds
2023-09-29 12:44:32.011  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusSubscription with name 'sub0' finished in 1 seconds
2023-09-29 12:44:32.011  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Creating ServiceBusSubscription with name 'sub0' ...
2023-09-29 12:44:32.011  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching Topic with name 'topic0' ...
2023-09-29 12:44:32.011  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusNamespace with name 'testrnddesb' ...
2023-09-29 12:44:33.610  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusNamespace with name 'testrnddesb' finished in 1 seconds
2023-09-29 12:44:34.419  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching Topic with name 'topic0' finished in 2 seconds
2023-09-29 12:44:34.419  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Creating Topic with name 'topic0' ...
2023-09-29 12:44:34.419  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusNamespace with name 'testrnddesb' ...
2023-09-29 12:44:36.079  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Fetching ServiceBusNamespace with name 'testrnddesb' finished in 1 seconds
2023-09-29 12:44:38.144  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Creating Topic with name 'topic0' finished in 3 seconds
2023-09-29 12:44:39.521  INFO 27842 --- [           main] c.a.s.c.r.i.crud.AbstractResourceCrud    : Creating ServiceBusSubscription with name 'sub0' finished in 7 seconds
2023-09-29 12:44:39.540  INFO 27842 --- [           main] o.s.c.stream.binder.BinderErrorChannel   : Channel 'topic0.sub0.errors' has 1 subscriber(s).
2023-09-29 12:44:39.541  INFO 27842 --- [           main] o.s.c.stream.binder.BinderErrorChannel   : Channel 'topic0.sub0.errors' has 2 subscriber(s).
2023-09-29 12:44:39.548  INFO 27842 --- [           main] AbstractAzureServiceClientBuilderFactory : Will configure the default credential of type DefaultAzureCredential for class com.azure.identity.ClientSecretCredentialBuilder.
2023-09-29 12:44:39.606  INFO 27842 --- [           main] c.a.m.s.i.ServiceBusConnectionProcessor  : {"az.sdk.message":"Setting next AMQP channel.","entityPath":"N/A"}
2023-09-29 12:44:39.607  INFO 27842 --- [           main] c.a.m.s.ServiceBusClientBuilder          : # of open clients with shared connection: 1
2023-09-29 12:44:39.618  INFO 27842 --- [           main] c.a.m.s.ServiceBusReceiverAsyncClient    : {"az.sdk.message":"Creating consumer.","linkName":"topic0/subscriptions/sub0_bd59d4_1695984279618","entityPath":"topic0/subscriptions/sub0"}
2023-09-29 12:44:39.622  INFO 27842 --- [           main] c.a.m.s.i.ServiceBusReceiveLinkProcessor : Requesting a new AmqpReceiveLink from upstream.
2023-09-29 12:44:39.624

I can see the topic topic0 subscription sub0created in portal.azure.com. However, I can also see that the app has issues when wants to use the topic and subscription.

2023-09-29 12:44:39.927  INFO 27842 --- [ctor-executor-1] c.a.c.a.i.handler.ReceiveLinkHandler     : {"az.sdk.message":"onLinkRemoteOpen","connectionId":"MF_4f992b_1695984279564","entityPath":"$cbs","linkName":"cbs:receiver","remoteSource":"Source{address='$cbs', durable=NONE, expiryPolicy=SESSION_END, timeout=0, dynamic=false, dynamicNodeProperties=null, distributionMode=null, filter=null, defaultOutcome=null, outcomes=null, capabilities=null}"}
2023-09-29 12:44:39.944 ERROR 27842 --- [ctor-executor-1] c.a.core.amqp.implementation.RetryUtil   : Failed to create receive link topic0/subscriptions/sub0_bd59d4_1695984279618
status-code: 401, status-description: LocalAuthDisabled: Authorization failed because SAS authentication has been disabled for the namespace. TrackingId:5e9ff2d0-343f-48ec-bd87-140f8291b4ae_G6, SystemTracker:NoSystemTracker, Timestamp:2023-09-29T10:44:39, errorContext[NAMESPACE: testrnddesb.servicebus.windows.net. ERROR CONTEXT: N/A, PATH: $cbs, REFERENCE_ID: cbs:receiver, LINK_CREDIT: 0]
2023-09-29 12:44:39.944  WARN 27842 --- [ctor-executor-1] c.a.m.s.i.ServiceBusReceiveLinkProcessor : {"az.sdk.message":"Non-retryable error occurred in AMQP receive link.","exception":"status-code: 401, status-description: LocalAuthDisabled: Authorization failed because SAS authentication has been disabled for the namespace. TrackingId:5e9ff2d0-343f-48ec-bd87-140f8291b4ae_G6, SystemTracker:NoSystemTracker, Timestamp:2023-09-29T10:44:39, errorContext[NAMESPACE: testrnddesb.servicebus.windows.net. ERROR CONTEXT: N/A, PATH: $cbs, REFERENCE_ID: cbs:receiver, LINK_CREDIT: 0]","linkName":"n/a","entityPath":"n/a"}
2023-09-29 12:44:39.944 ERROR 27842 --- [ctor-executor-1] .s.FluxAutoLockRenew$LockRenewSubscriber : Errors occurred upstream.
status-code: 401, status-description: LocalAuthDisabled: Authorization failed because SAS authentication has been disabled for the namespace. TrackingId:5e9ff2d0-343f-48ec-bd87-140f8291b4ae_G6, SystemTracker:NoSystemTracker, Timestamp:2023-09-29T10:44:39, errorContext[NAMESPACE: testrnddesb.servicebus.windows.net. ERROR CONTEXT: N/A, PATH: $cbs, REFERENCE_ID: cbs:receiver, LINK_CREDIT: 0]
2023-09-29 12:44:39.946  INFO 27842 --- [ctor-executor-1] c.a.m.s.ServiceBusProcessorClient        : Error receiving messages.
2023-09-29 12:44:39.950 ERROR 27842 --- [ctor-executor-1] .s.i.s.i.ServiceBusInboundChannelAdapter : Error in the operation RECEIVE occurred on entity topic0/subscriptions/sub0. Error: {}

com.azure.messaging.servicebus.ServiceBusException: status-code: 401, status-description: LocalAuthDisabled: Authorization failed because SAS authentication has been disabled for the namespace. TrackingId:5e9ff2d0-343f-48ec-bd87-140f8291b4ae_G6, SystemTracker:NoSystemTracker, Timestamp:2023-09-29T10:44:39, errorContext[NAMESPACE: testrnddesb.servicebus.windows.net. ERROR CONTEXT: N/A, PATH: $cbs, REFERENCE_ID: cbs:receiver, LINK_CREDIT: 0]
    at com.azure.messaging.servicebus.ServiceBusReceiverAsyncClient.mapError(ServiceBusReceiverAsyncClient.java:1676) ~[azure-messaging-servicebus-7.14.0.jar:7.14.0]
    at com.azure.messaging.servicebus.ServiceBusReceiverAsyncClient.lambda$receiveMessagesWithContext$21(ServiceBusReceiverAsyncClient.java:892) ~[azure-messaging-servicebus-7.14.0.jar:7.14.0]
    at reactor.core.publisher.Flux.lambda$onErrorMap$28(Flux.java:7070) ~[reactor-core-3.4.26.jar:3.4.26]
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.4.26.jar:3.4.26]
    at reactor.core.publisher.FluxPublishOn$PublishOnSubscriber.doError(FluxPublishOn.java:511) ~[reactor-core-3.4.26.jar:3.4.26]

Why is it allowing us to create the topic and at the same time complain about using it with an Authorization failed because SAS authentication has been disabled for the namespace?

I continued exploring alternatives and I ended up trying the same configuration above but using spring.cloud.azure.servicebus instead of spring.cloud.azure.

spring:
  cloud:
    azure:
      servicebus:
        enabled: true
        entity-type: topic
        credential:
          client-id: ${AZURE_CLIENT_ID}
          client-secret: ${AZURE_CLIENT_SECRET}
        profile:
          tenant-id: ${AZURE_TENANT_ID}
          subscription-id: ${AZURE_SUBSCRIPTION_ID}
        resource:
          resource-group:  ${AZURE_RESOURCE_GROUP}
        namespace: ${SERVICEBUS_NAMESPACE}
    stream:
      function.definition: consume
      bindings:
        consume-in-0.destination: topic0
        consume-in-0.group: sub0
        hello-out-0.destination: topic0
      servicebus:
        bindings:
          consume-in-0.consumer.auto-complete: false
          hello-out-0.producer.entity-type: topic
      default-binder: servicebus

With that configuration works fine accessing the topics and allowing us to send a receive messages. ONLY if the topic topic0 and subscription sub0 are created manually using the portal.azure.com. With this configuration, the automatic creation of topics and subscriptions is not happening.

In spring-cloud-azure-stream-binder-servicebus, I can see in ServiceBusBinderConfiguration two ProvisioningProvider: ServiceBusChannelResourceManagerProvisioner and ServiceBusChannelProvisioner. The logic for creating topics and subscriptions seems to be only present in ServiceBusChannelResourceManagerProvisioner. When our configuration uses the prefix spring.cloud.azure.servicebus the bean ServiceBusChannelProvisioner is used and it does not have any topic creation logic.

Setup:

Can someone help me with the correct configuration for spring apps with credentials as service principal and auto-creation of topics at application startup?

Information Checklist Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

joshfree commented 11 months ago

@yiliuTo could you please assist?

Netyyyy commented 11 months ago

Hi @fredysierra , thanks for reaching out. We have received your submission and will take it into consideration. We appreciate your input and will review this matter as soon as possible. Please feel free to provide any additional information or context that you think may be helpful. We'll keep you updated on the progress of our review. Thank you for your contribution to improving our project.

moarychan commented 11 months ago

Hi @fredysierra , I have reproduced your scenario. Only ServiceBusChannelResourceManagerProvisioner will create entities, ServiceBusChannelProvisioner will not, which will only work without creating entities. The root cause is that the we use Azure ARM client to get the connection string, and it still can respond to the connection string even if the Service Bus has disabled the local authentication, the priority of connection-string will be higher than the service principal, the subsequent ServiceBusProcessorClientBuilder will use this invalid connecting-string, and then encounter this error.

A quick fix is to override this bean and return null when it gets the connection-string using the ARM client, add the below bean definition in your configuration:

@Bean
ServiceBusArmConnectionStringProvider serviceBusArmConnectionStringProvider(AzureResourceManager azureResourceManager, ServiceBusResourceMetadata resourceMetadata) {
    return new MyServiceBusArmConnectionStringProvider(azureResourceManager, resourceMetadata, resourceMetadata.getName());
}

class MyServiceBusArmConnectionStringProvider extends ServiceBusArmConnectionStringProvider {
    public MyServiceBusArmConnectionStringProvider(AzureResourceManager azureResourceManager, AzureResourceMetadata azureResourceMetadata, String namespace) {
        super(azureResourceManager, azureResourceMetadata, namespace);
    }

    @Override
    public String getConnectionString() {
        return null;
    }
}
fredysierra commented 11 months ago

Thank you for your quick fix @moarychan. I can see it is working for me now. Are there any plans to include this as part of a patch or new version?

saragluna commented 10 months ago

@fredysierra this approach is just a workaround, and we won't include this into the code. In my understanding, the possible solution for this is to redesign the authentication for sdk client in Spring, for users to be able to specify which kind of auth they want to use for the sdk. Now we are using an opinionated way to configure the auth order, which is connection string, and token credential. But this change will take some time to discuss and design, so there's no target date for a release.