Skip to content

Commit

Permalink
[Messaging] Relax emulator endpoint restrictions (#44052)
Browse files Browse the repository at this point in the history
The focus of these changes is to remove the restriction
that the endpoint used in connection strings must
resolve to a variant of `localhost` when using the
development emulator.

A fix to parsing was also made to be resilient to
an endpoint with no scheme that specifies a custom port.
Previously, this would parse incorrectly and be
rejected.  The scenario is now detected and parsed.

Also included is a package bump for the AMQP transport
library and release prep for the Event Hubs core package.
  • Loading branch information
jsquire authored May 15, 2024
1 parent 7fd0fbc commit 099476d
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 90 deletions.
2 changes: 1 addition & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<PackageReference Update="Azure.ResourceManager.Storage" Version="1.2.1" />

<!-- Other approved packages -->
<PackageReference Update="Microsoft.Azure.Amqp" Version="2.6.6" />
<PackageReference Update="Microsoft.Azure.Amqp" Version="2.6.7" />
<PackageReference Update="Microsoft.Azure.WebPubSub.Common" Version="1.2.0" />
<PackageReference Update="Microsoft.Identity.Client" Version="4.60.3" />
<PackageReference Update="Microsoft.Identity.Client.Extensions.Msal" Version="4.60.3" />
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,6 @@
<data name="TroubleshootingGuideLink" xml:space="preserve">
<value>For troubleshooting information, see https://aka.ms/azsdk/net/eventhubs/exceptions/troubleshoot</value>
</data>
<data name="InvalidEmulatorEndpoint" xml:space="preserve">
<value>The Event Hubs emulator is only available locally. The endpoint must reference to the local host.</value>
</data>
<data name="BufferedProducerStartupTimeout" xml:space="preserve">
<value>The buffered producer took too long to start.</value>
</data>
Expand Down
12 changes: 6 additions & 6 deletions sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Release History

## 5.12.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes
## 5.11.3 (2024-05-15)

### Bugs Fixed

- Fixed an error that caused connection strings using host names without a scheme to fail parsing and be considered invalid.

### Other Changes

- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.6, which includes a bug fix for an internal `NullReferenceException` that would sometimes impact creating new links. _(see: [#258](https://github.com/azure/azure-amqp/issues/258))_
- Removed the restriction that endpoints used with the development emulator had to resolve to a `localhost` variant.

- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.7, which contains several bug fixes, including for an internal `NullReferenceException` that would sometimes impact creating new links. _(see: [#258](https://github.com/azure/azure-amqp/issues/258))_

## 5.11.2 (2024-04-10)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Azure Event Hubs is a highly scalable publish-subscribe service that can ingest millions of events per second and stream them to multiple consumers. This client library allows for both publishing and consuming events using Azure Event Hubs. For more information about Event Hubs, see https://azure.microsoft.com/en-us/services/event-hubs/</Description>
<Version>5.12.0-beta.1</Version>
<Version>5.11.3</Version>
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
<ApiCompatVersion>5.11.2</ApiCompatVersion>
<PackageTags>Azure;Event Hubs;EventHubs;.NET;AMQP;IoT;$(PackageCommonTags)</PackageTags>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Azure.Core;

Expand Down Expand Up @@ -244,13 +245,6 @@ internal void Validate(string explicitEventHubName,
{
throw new ArgumentException(Resources.MissingConnectionInformation, connectionStringArgumentName);
}

// Ensure that the namespace reflects the local host when the development emulator is being used.

if ((UseDevelopmentEmulator) && (!Endpoint.IsLoopback))
{
throw new ArgumentException(Resources.InvalidEmulatorEndpoint, connectionStringArgumentName);
}
}

/// <summary>
Expand Down Expand Up @@ -334,6 +328,17 @@ public static EventHubsConnectionStringProperties Parse(string connectionString)
{
endpointUri = null;
}
else if (string.IsNullOrEmpty(endpointUri.Host) && (CountChar(':', value.AsSpan()) == 1))
{
// If the host was empty after parsing and the value has a single port/scheme separator,
// then the parsing likely failed to recognize the host due to the lack of a scheme. Add
// an artificial scheme and try to parse again.

if (!Uri.TryCreate(string.Concat(EventHubsEndpointScheme, value), UriKind.Absolute, out endpointUri))
{
endpointUri = null;
}
}

var endpointBuilder = endpointUri switch
{
Expand Down Expand Up @@ -400,5 +405,30 @@ public static EventHubsConnectionStringProperties Parse(string connectionString)

return parsedValues;
}

/// <summary>
/// Counts the number of times a character occurs in a given span.
/// </summary>
///
/// <param name="span">The span to evaluate.</param>
/// <param name="value">The character to count.</param>
///
/// <returns>The number of times the <paramref name="value"/> occurs in <paramref name="span"/>.</returns>
///
private static int CountChar(char value,
ReadOnlySpan<char> span)
{
var count = 0;

foreach (var character in span)
{
if (character == value)
{
++count;
}
}

return count;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ public void ParseIgnoresUnknownTokens()
[TestCase("amqp://test.endpoint.com")]
[TestCase("http://test.endpoint.com")]
[TestCase("https://test.endpoint.com:8443")]
public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue)
public void ParseAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue)
{
var connectionString = $"Endpoint={ endpointValue };EntityPath=dummy";
var parsed = EventHubsConnectionStringProperties.Parse(connectionString);
Expand All @@ -371,7 +371,7 @@ public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue)
/// </summary>
///
[Test]
[TestCase("test.endpoint.com:443")]
[TestCase("test-endpoint|com:443")]
[TestCase("notvalid=[broke]")]
public void ParseDoesNotAllowAnInvalidEndpointFormat(string endpointValue)
{
Expand Down Expand Up @@ -402,6 +402,7 @@ public void ParseConsidersMissingValuesAsMalformed(string connectionString)
/// method.
/// </summary>
///
[Test]
public void ParseDetectsDevelopmentEmulatorUse()
{
var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true";
Expand All @@ -416,6 +417,7 @@ public void ParseDetectsDevelopmentEmulatorUse()
/// method.
/// </summary>
///
[Test]
public void ParseRespectsDevelopmentEmulatorValue()
{
var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=false";
Expand All @@ -425,6 +427,57 @@ public void ParseRespectsDevelopmentEmulatorValue()
Assert.That(parsed.UseDevelopmentEmulator, Is.False, "The development emulator flag should have been unset.");
}

/// <summary>
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Parse" />
/// method.
/// </summary>
///
[Test]
[TestCase("localhost")]
[TestCase("localhost:9084")]
[TestCase("127.0.0.1")]
[TestCase("local.docker.com")]
[TestCase("local.docker.com:8080")]
[TestCase("www.fake.com")]
[TestCase("www.fake.com:443")]
public void ParseRespectsTheEndpointForDevelopmentEmulatorValue(string host)
{
var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true";
var endpoint = new Uri(string.Concat(GetEventHubsEndpointScheme(), host));
var parsed = EventHubsConnectionStringProperties.Parse(connectionString);

Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match.");
Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match.");
Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set.");
}

/// <summary>
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Parse" />
/// method.
/// </summary>
///
[Test]
[TestCase("sb://localhost")]
[TestCase("http://localhost:9084")]
[TestCase("sb://local.docker.com")]
[TestCase("amqps://local.docker.com:8080")]
[TestCase("sb://www.fake.com")]
[TestCase("amqp://www.fake.com:443")]
public void ParseRespectsTheUrlFormatEndpointForDevelopmentEmulatorValue(string host)
{
var endpoint = new UriBuilder(host)
{
Scheme = GetEventHubsEndpointScheme()
};

var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true";
var parsed = EventHubsConnectionStringProperties.Parse(connectionString);

Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match.");
Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match.");
Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set.");
}

/// <summary>
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Parse" />
/// method.
Expand Down Expand Up @@ -657,33 +710,6 @@ public void ValidateAllowsSharedAccessSignatureAuthorization()
Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access signature authorization.");
}

/// <summary>
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Validate" />
/// method.
/// </summary>
///
[Test]
[TestCase("localhost", true)]
[TestCase("127.0.0.1", true)]
[TestCase("www.microsoft.com", false)]
[TestCase("fake.servicebus.windows.net", false)]
[TestCase("weirdname:8080", false)]
public void ValidateRequiresLocalEndpointForDevelopmentEmulator(string endpoint,
bool isValid)
{
var fakeConnection = $"Endpoint=sb://{ endpoint };SharedAccessSignature=[not_real];UseDevelopmentEmulator=true";
var properties = EventHubsConnectionStringProperties.Parse(fakeConnection);

if (isValid)
{
Assert.That(() => properties.Validate("fake", "dummy"), Throws.Nothing, "Validation should allow a local endpoint.");
}
else
{
Assert.That(() => properties.Validate("fake", "dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.InvalidEmulatorEndpoint), "Validation should enforce that the endpoint is a local address.");
}
}

/// <summary>
/// Compares two <see cref="EventHubsConnectionStringProperties" /> instances for
/// structural equality.
Expand Down
4 changes: 4 additions & 0 deletions sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

### Bugs Fixed

- Fixed an error that caused connection strings using host names without a scheme to fail parsing and be considered invalid.

### Other Changes

- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.7, which contains a fix for decoding messages with a null format code as the body.

## 7.18.0-beta.1 (2024-05-08)

### Features Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,17 @@ public static ServiceBusConnectionStringProperties Parse(string connectionString
{
endpointUri = null;
}
else if (string.IsNullOrEmpty(endpointUri.Host) && (CountChar(':', value.AsSpan()) == 1))
{
// If the host was empty after parsing and the value has a single port/scheme separator,
// then the parsing likely failed to recognize the host due to the lack of a scheme. Add
// an artificial scheme and try to parse again.

if (!Uri.TryCreate($"{ServiceBusEndpointSchemeName}://{value}", UriKind.Absolute, out endpointUri))
{
endpointUri = null;
}
}

var endpointBuilder = endpointUri switch
{
Expand Down Expand Up @@ -371,14 +382,32 @@ public static ServiceBusConnectionStringProperties Parse(string connectionString
lastPosition = currentPosition;
}

// Enforce that the development emulator can only be used for local development.
return parsedValues;
}

if ((parsedValues.UseDevelopmentEmulator) && (!parsedValues.Endpoint.IsLoopback))
/// <summary>
/// Counts the number of times a character occurs in a given span.
/// </summary>
///
/// <param name="span">The span to evaluate.</param>
/// <param name="value">The character to count.</param>
///
/// <returns>The number of times the <paramref name="value"/> occurs in <paramref name="span"/>.</returns>
///
private static int CountChar(char value,
ReadOnlySpan<char> span)
{
var count = 0;

foreach (var character in span)
{
throw new ArgumentException("The Service Bus emulator is only available locally. The endpoint must reference to the local host.", connectionString);
if (character == value)
{
++count;
}
}

return parsedValues;
return count;
}
}
}
Loading

0 comments on commit 099476d

Please sign in to comment.