question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG] Blob storage service/container/blob clients drop account name for emulator connection string with custom domain name and port number

See original GitHub issue

Describe the bug

Give the following connection string:

AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://host.docker.internal:10010/devstoreaccount1;

BlobServiceClient is constructed incorrectly, with missing AccountName

As a result of that, the subsequent BlobContainerClient and BlobClient created from the service client, are also malformed.

var serviceClient = new BlobServiceClient(connectionString);
var containerClient = serviceClient.GetBlobContainerClient("containername"); 
var blobClient = containerClient.GetBlobClient("abc.txt");

results in

and

The missing AccountName is causing the blob URI to be malformed: http://host.docker.internal:10010/devstoreaccount1/abc.txt, resulting in blob operations failing.

Expected behavior

BlobClient should have account name should be detected properly (known emulator account name), preserved and blob operations succeed.

Actual behavior (include Exception or Stack Trace)

To Reproduce

[Fact]
public void Use_custom_host_name_and_port_for_emulator()
{
    var connectionString = string.Format("AccountName={0};AccountKey={1};BlobEndpoint={2};",
        "devstoreaccount1", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", "http://host.docker.internal:10010/devstoreaccount1");
    var containerName = "containername";
    var blobName = "abc.txt";

    var serviceClient = new BlobServiceClient(connectionString);
    var containerClient = serviceClient.GetBlobContainerClient(containerName); //new BlobContainerClient(connectionString, containerName);
    var blobClient = containerClient.GetBlobClient(blobName);

    Assert.Equal(10_010, blobClient.Uri.Port);
    Assert.Equal("devstoreaccount1", blobClient.AccountName); // ❌ ""
    Assert.Equal("containername", blobClient.BlobContainerName); // ❌ "devstoreaccount1"
    Assert.Equal("abc.txt", blobClient.Name); // ❌ "containername/abc.txt"
}

Environment:

  • Name and version of the Library package used: Azure.Storage.Blobs 12.x
  • Windows 10
  • .NET Core 3.1

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:10 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
bsegaultcommented, Mar 31, 2023

Hi,

We also encounter the same issue with the default connection string given by Azurite: DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;

From what I can see, the logic is failing in the following callStack:

extractConnectionStringParts(connectionString) getAccountNameFromUrl(blobEndpoint) isIpEndpointStyle(parsedUrl)

In this later function, there is an exception for localhost, but not for host.docker.internal:

function isIpEndpointStyle(parsedUrl) {
    if (parsedUrl.getHost() === undefined) {
        return false;
    }
    const host = parsedUrl.getHost() + (parsedUrl.getPort() === undefined ? "" : ":" + parsedUrl.getPort());
    // Case 1: Ipv6, use a broad regex to find out candidates whose host contains two ':'.
    // Case 2: localhost(:port), use broad regex to match port part.
    // Case 3: Ipv4, use broad regex which just check if host contains Ipv4.
    // For valid host please refer to https://man7.org/linux/man-pages/man7/hostname.7.html.
    return (/^.*:.*:.*$|^localhost(:[0-9]+)?$|^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])){3}(:[0-9]+)?$/.test(host) ||
        (parsedUrl.getPort() !== undefined && PathStylePorts.includes(parsedUrl.getPort())));
}

Changing the regex to /^.*:.*:.*$|^(localhost|host.docker.internal)(:[0-9]+)?$|^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])){3}(:[0-9]+)?$/ solves our issue.

OR better yet, in extractConnectionStringParts(connectionString), we could check if there is an AccountName before checking from the URL:

original:

    else {
        // SAS connection string
        const accountSas = getValueInConnString(connectionString, "SharedAccessSignature");
        const accountName = getAccountNameFromUrl(blobEndpoint);
        if (!blobEndpoint) {
            throw new Error("Invalid BlobEndpoint in the provided SAS Connection String");
        }
        else if (!accountSas) {
            throw new Error("Invalid SharedAccessSignature in the provided SAS Connection String");
        }
        return { kind: "SASConnString", url: blobEndpoint, accountName, accountSas };
    }

suggestion:

    else {
        // SAS connection string
        const accountSas = getValueInConnString(connectionString, "SharedAccessSignature");
        let accountName = getValueInConnString(connectionString, "AccountName");
        if (!accountName) {
            accountName = getAccountNameFromUrl(blobEndpoint);
        }
        if (!blobEndpoint) {
            throw new Error("Invalid BlobEndpoint in the provided SAS Connection String");
        }
        else if (!accountSas) {
            throw new Error("Invalid SharedAccessSignature in the provided SAS Connection String");
        }
        return { kind: "SASConnString", url: blobEndpoint, accountName, accountSas };
    }
1reaction
jsquirecommented, Jun 22, 2021

Hi @SeanFeldman. Happy to offer what context I can. In this case, my contribution is purely procedural. Any issue tagged with bug or feature is required to have a milestone associated with it. Issues are also required to have only one of bug, feature, and question. Weekly, validation takes place and generates a report listing issues in an invalid state.

Once the bug label was added, the issue started failing validation and alerting the triage team. Since bug was added by the team that owns the issue, it was given precedence and question was removed. By default issues with no milestone are shifted to backlog until the team that owns them decides to schedule them. Each team has a different process for triage and scheduling, so with respect to how this will be prioritized and scheduled by the Storage team, I’ll need to defer to @kasobol-msft.

Read more comments on GitHub >

github_iconTop Results From Across the Web

[BUG] BlobServiceClient can't parse connection string with ...
[BUG] BlobServiceClient can't parse connection string with custom domain and not port 11002 #27126.
Read more >
Configure a connection string - Azure Storage
You can configure connection strings to: Connect to the Azurite storage emulator. Access a storage account in Azure. Access specified resources ...
Read more >
Use the Azure Storage Emulator for development and ...
The Microsoft Azure Storage Emulator is a tool that emulates the Azure Blob, Queue, and Table services for local development purposes.
Read more >
Connect to Azure blob storage on custom domain
I can download it through the Nuget package called Azure.Storage.Blobs with this connection string. DefaultEndpointsProtocol=https;AccountName= ...
Read more >
windows azure development storage blob service not starting
In my experience, this error is usually a port conflict, and BitTorrent does typically ... well-known storage emulator account name and key.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found