[QUERY] Receiving Unauthorized Access Error on RenewToken Periodically with Azure Service Bus Queue Listener
See original GitHub issueI am receiving a Microsoft.Azure.ServiceBus.ServiceBusException (message below with sensitive information removed) periodically in my queue receiver. The SAS key has send/listen access and the error seems inconsequential as processing continues as normal. However, the message is creating a signal to noise problem in my dashboards (receiving 10-70 errors per day). Any ideas on why this is happening? The listener is running in an Azure App Service, but I don’t think that matters. I have adjusted my retry logic to use a RetryExponential with a 1 second to 1 minute backoff with 5 retries.
Original StackOverflow Question
Packages
Net Core 3.1
Microsoft.Azure.ServiceBus, Version=4.1.3.0, Culture=neutral, PublicKeyToken=7e34167dcc6d6d8c
Error Message
The link ‘xxx;xxx:xxx:xxx:source(address:xxx):xxx’ is force detached. Code: RenewToken. Details: Unauthorized access. ‘Listen’ claim(s) are required to perform this operation. Resource: ‘sb://xxx.servicebus.windows.net/xxx’… TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04 The link ‘xxx;xxx:xxx:xxx:source(address:xxx):xxx’ is force detached. Code: RenewToken. Details: Unauthorized access. ‘Listen’ claim(s) are required to perform this operation. Resource: ‘sb://xxx.servicebus.windows.net/xxx’… TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04
Source
internal delegate TClient ClientFactory<out TClient>(string connectionString, string entityPath,
RetryPolicy retryPolicy);
internal delegate Task OnMessageCallback<in TMessage>(TMessage message,
CancellationToken cancellationToken = default) where TMessage : ICorrelative;
internal sealed class ReceiverClientWrapper<TMessage> : IReceiverClientWrapper<TMessage>
where TMessage : ICorrelative
{
// ReSharper disable once StaticMemberInGenericType
private static readonly Regex TransientConnectionErrorRegex =
new Regex(
@"(The link '([a-f0-9-]+);([0-9]*:)*source\(address:([a-z0-9_]+)\):([a-z0-9_]+)' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim\(s\) are required to perform this operation. Resource: 'sb:\/\/([a-z0-9-_.\/]+)'.. TrackingId:([a-z0-9_]+), SystemTracker:([a-z0-9]+), Timestamp:([0-9]{4}(-[0-9]{2}){2}T([0-9]{2}:){2}[0-9]{2}) )+",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
private readonly IReceiverClient _receiverClient;
private readonly IMessageConverter<TMessage> _messageConverter;
private readonly ILogger _logger;
private readonly int _maximumConcurrency;
public ReceiverClientWrapper(IReceiverClient receiverClient, IMessageConverter<TMessage> messageConverter,
ILogger logger, int maximumConcurrency)
{
_receiverClient = receiverClient;
_messageConverter = messageConverter;
_logger = logger;
_maximumConcurrency = maximumConcurrency;
}
public Task SubscribeAsync(OnMessageCallback<TMessage> onMessageCallback,
OnFailureCallback onFailureCallback, CancellationToken cancellationToken = default)
{
var messageHandlerOptions = CreateMessageHandlerOptions(onFailureCallback, cancellationToken);
async Task Handler(Message message, CancellationToken token)
{
var convertedMessage = _messageConverter.Convert(message);
await onMessageCallback(convertedMessage, cancellationToken);
await _receiverClient.CompleteAsync(message.SystemProperties.LockToken);
}
_receiverClient.RegisterMessageHandler(Handler, messageHandlerOptions);
return Task.CompletedTask;
}
private MessageHandlerOptions CreateMessageHandlerOptions(OnFailureCallback onFailureCallback,
CancellationToken cancellationToken)
{
async Task HandleExceptionAsync(ExceptionReceivedEventArgs arguments)
{
var exception = arguments.Exception;
if (TransientConnectionErrorRegex.IsMatch(exception.Message))
{
_logger.LogWarning(exception, @"Transient connectivity error occurred");
return;
}
await onFailureCallback(exception, cancellationToken);
}
return new MessageHandlerOptions(HandleExceptionAsync)
{
AutoComplete = false,
MaxConcurrentCalls = _maximumConcurrency
};
}
public async ValueTask DisposeAsync()
{
await _receiverClient.CloseAsync();
}
}
internal sealed class SenderClientWrapper<TMessage> : ISenderClientWrapper<TMessage> where TMessage : ICorrelative
{
private readonly ISenderClient _senderClient;
private readonly IMessageConverter<TMessage> _messageConverter;
public SenderClientWrapper(ISenderClient senderClient, IMessageConverter<TMessage> messageConverter)
{
_senderClient = senderClient;
_messageConverter = messageConverter;
}
public Task SendAsync(TMessage message, CancellationToken cancellationToken = default)
{
var internalMessage = _messageConverter.Convert(message);
return _senderClient.SendAsync(internalMessage);
}
public Task SendAsync(IEnumerable<TMessage> messages, CancellationToken cancellationToken = default)
{
var internalMessages = messages
.Select(_messageConverter.Convert)
.ToImmutableArray();
return _senderClient.SendAsync(internalMessages);
}
public async ValueTask DisposeAsync()
{
await _senderClient.CloseAsync();
}
}
internal abstract class AbstractClientWrapperFactory
{
private const int MaximumRetryCount = 5;
private static readonly TimeSpan MinimumRetryBackOff = TimeSpan.FromSeconds(1);
private static readonly TimeSpan MaximumRetryBackOff = TimeSpan.FromMinutes(1);
protected AbstractClientWrapperFactory(IOptions<MessageBusConfiguration> options)
{
Options = options;
}
protected IOptions<MessageBusConfiguration> Options { get; }
protected static string GetEntityPath<TMessage>() where TMessage : class
{
var messageAttribute = typeof(TMessage).GetCustomAttribute<AbstractMessageAttribute>();
if (messageAttribute == null)
{
throw new ArgumentException($@"Message requires {nameof(AbstractMessageAttribute)}");
}
return messageAttribute.EntityName;
}
protected TClient CreateClientEntity<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class
{
var entityPath = GetEntityPath<TMessage>();
var retryPolicy = CreateRetryPolicy();
return clientFactory(Options.Value.ConnectionString, entityPath, retryPolicy);
}
protected static IQueueClient QueueClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new QueueClient(connectionString, entityPath, retryPolicy: retryPolicy);
}
private static RetryPolicy CreateRetryPolicy()
{
return new RetryExponential(MinimumRetryBackOff, MaximumRetryBackOff, MaximumRetryCount);
}
}
internal sealed class SenderClientWrapperFactory : AbstractClientWrapperFactory, ISenderClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
public SenderClientWrapperFactory(IMessageConverterFactory messageConverterFactory,
IOptions<MessageBusConfiguration> options) : base(options)
{
_messageConverterFactory = messageConverterFactory;
}
public ISenderClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateWrapper<TEvent, ITopicClient>(TopicClientFactory);
}
public ISenderClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private ISenderClientWrapper<TMessage> CreateWrapper<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : ISenderClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new SenderClientWrapper<TMessage>(clientEntity, messageConverter);
}
private static ITopicClient TopicClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new TopicClient(connectionString, entityPath, retryPolicy);
}
}
internal sealed class ReceiverClientWrapperFactory : AbstractClientWrapperFactory, IReceiverClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
private readonly ILogger<ReceiverClientWrapperFactory> _logger;
public ReceiverClientWrapperFactory(IOptions<MessageBusConfiguration> options,
IMessageConverterFactory messageConverterFactory,
ILogger<ReceiverClientWrapperFactory> logger) : base(options)
{
_messageConverterFactory = messageConverterFactory;
_logger = logger;
}
public IReceiverClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateReceiverClientWrapper<TEvent, ISubscriptionClient>(SubscriptionClientFactory);
}
public IReceiverClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateReceiverClientWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private IReceiverClientWrapper<TMessage> CreateReceiverClientWrapper<TMessage, TClient>(
ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : IReceiverClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new ReceiverClientWrapper<TMessage>(clientEntity, messageConverter, _logger,
Options.Value.MaximumConcurrency);
}
private ISubscriptionClient SubscriptionClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new SubscriptionClient(connectionString, entityPath, Options.Value.SubscriberName,
retryPolicy: retryPolicy);
}
}
internal sealed class RequestService<TRequest> : IRequestService<TRequest> where TRequest : class, IRequest
{
private readonly Lazy<ISenderClientWrapper<TRequest>> _senderClient;
private readonly Lazy<IReceiverClientWrapper<TRequest>> _receiverClient;
public RequestService(ISenderClientWrapperFactory senderClientWrapperFactory,
IReceiverClientWrapperFactory receiverClientWrapperFactory)
{
_senderClient =
new Lazy<ISenderClientWrapper<TRequest>>(senderClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
_receiverClient
= new Lazy<IReceiverClientWrapper<TRequest>>(receiverClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
}
public Task PublishRequestAsync(TRequest requestMessage, CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessage, cancellationToken);
}
public Task PublishRequestAsync(IEnumerable<TRequest> requestMessages,
CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessages, cancellationToken);
}
public Task SubscribeAsync(OnRequestCallback<TRequest> onRequestCallback, OnFailureCallback onFailureCallback,
CancellationToken cancellationToken = default)
{
return _receiverClient
.Value
.SubscribeAsync((message, token) => onRequestCallback(message, cancellationToken), onFailureCallback,
cancellationToken);
}
public async ValueTask DisposeAsync()
{
if (_senderClient.IsValueCreated)
{
await _senderClient.Value.DisposeAsync();
}
if (_receiverClient.IsValueCreated)
{
await _receiverClient.Value.DisposeAsync();
}
}
public Task ThrowIfNotReadyAsync(CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(ImmutableArray<TRequest>.Empty, cancellationToken);
}
}
Issue Analytics
- State:
- Created 3 years ago
- Reactions:4
- Comments:32
Top GitHub Comments
@3437CasaVerde After the move to RootManageSharedAccessKey, I have not gotten anymore renewal exceptions, but I am seeing ServiceBusCommunicationExceptions with an inner exception of OperationCancelledExceptions. I don’t know if this is related or not? I have multiple receivers (several topics/queues) and they all seem to disconnect at roughly the same time. If it is an expiration problem, they likely all expire and then reconnect together. This is obviously problematic, I would like that to be isolated to the SDK if it is a known condition of Azure Service Bus. In addition, using RootManageSharedAccessKey is also a security risk long term, so your investigation is still invaluable. I don’t know if this helps or increases confusion.
That’s the same for us. That error, based on my understanding, does not make sense because by definition the root access key should have all the permission claims.
We are getting several strange and unclear errors from the .NET sdk from an ASP.NET core 2.2 application. Our errors are described in this issue. We are not currently able to constantly reproduce them.
I have maybe a good news.
We have rewritten our code to interact with the Azure service bus by using the new .NET sdk named Azure.Messaging.ServiceBus. We are still testing the new implementation, but it seems that the errors have disappeared. So I would suggest you to evaluate its adoption as a mitigation strategy.
If you still want to use the Microsoft.Azure.ServiceBus package (instead of the new one mentioned above) you can follow this guide to programmatically get all the logs raised from the Microsoft.Azure.ServiceBus code. By doing so, you can perform a verbose logging of all the operations done by the library. I have used these logs to open my own issue. I haven’t understood the root cause of the errors, but at least now I have plenty of exception stacktraces. You can use them to open a ticket to the Azure support if you like.
From our side, we hope that the new Azure.Messaging.ServiceBus SDK has finally solved our troubles. Some colleagues from other teams have suggested us to give it a shot, because they have experienced several improvements in their own projects by adopting it.