Skip to content

AzBlob SAS URL doesn't work if Azurite is run on a custom hostname. #23496

Open
@vikblom

Description

@vikblom

Bug Report

Hello 👋

I'm using the azblob package and had some trouble running tests against Azurite.

These are the versions I'm using:

# import (
#    "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
#    "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
# )

> go list -m github.com/Azure/azure-sdk-for-go/sdk/storage/azblob
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1

> go version
go version go1.23.1 darwin/arm64

> docker image ls | grep azurite
mcr.microsoft.com/azure-storage/azurite                   3.32.0            8ab49b8b5104 

What happened?

I have some Go code that uses azblob which is backed by Azurite when testing.

This all works fine when my tests are running locally, and Azurite is available on 127.0.0.1.
The full URLs are something like

http://127.0.0.1:10000/devstoreaccount1/mycontainer/...

I can create a container, create a blob, create a SAS URL for that blob, and download it OK.

However, if Azurite is on a custom hostname (for example in Gitlab CI, or when tests are also run inside docker), then GET-ing the SAS URL fails with

        <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        <Error>
          <Code>AuthorizationFailure</Code>
          <Message>Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.
        RequestId:de7d4aa7-8755-47ce-b53b-d8b45449b50b
        Time:2024-09-25T11:00:30.694Z</Message>
        </Error>

In this case the full URL is something like:

http://azurite:10000/devstoreaccount1/mycontainer/...

Note that I can see in the Azurite logs that creating the container and blob was successful (HTTP 200).

What did you expect or want to happen?

Expected http.Get(url) to work when url was created without error.

How can we reproduce it?

go.mod
module reprod

go 1.23.1

require github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1

require (
	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
	golang.org/x/net v0.27.0 // indirect
	golang.org/x/text v0.16.0 // indirect
)
reprod_test.go
package azblob

import (
	"cmp"
	"context"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"testing"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
)

const (
	accountName = "devstoreaccount1"
	accountKey  = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
	container   = "mycontainer"
	key         = "mykey"
	content     = "lorem ipsum"
)

func TestAzurite(t *testing.T) {
	azuriteURL := &url.URL{
		Scheme: "http",
		Host: net.JoinHostPort(
			cmp.Or(
				os.Getenv("AZURITE_HOST"),
				"127.0.0.1",
			),
			"10000",
		),
		Path: accountName,
	}
	t.Logf("URL: %s", azuriteURL.String())

	creds, err := azblob.NewSharedKeyCredential(accountName, accountKey)
	if err != nil {
		t.Fatalf("new keys")
	}
	client, err := azblob.NewClientWithSharedKeyCredential(azuriteURL.String(), creds, nil)
	if err != nil {
		t.Fatalf("new client: %s", err)
	}

	// Create container
	_, err = client.CreateContainer(context.Background(), container, nil)
	if bloberror.HasCode(err, bloberror.ContainerAlreadyExists) {
		err = nil
	}
	if err != nil {
		t.Fatalf("create container: %s", err)
	}

	// Upload.
	_, err = client.UploadBuffer(context.Background(), container, key, []byte(content), nil)
	if err != nil {
		t.Fatalf("upload buf: %s", err)
	}

	// Create and use SAS URL.
	bc := client.ServiceClient().NewContainerClient(container).NewBlobClient(key)
	sasURL, err := bc.GetSASURL(
		sas.BlobPermissions{Read: true},
		time.Now().Add(time.Hour),
		nil,
	)
	if err != nil {
		t.Fatalf("Get SAS URL: %s", err)
	}
	t.Logf("SAS URL: %s", sasURL)

	resp, err := http.Get(sasURL)
	if err != nil {
		bs, _ := io.ReadAll(resp.Body)
		t.Fatalf("get sas url: %s\n%s", err, bs)
	}

	got, _ := io.ReadAll(resp.Body)
	t.Logf("Got content: %s", got)
}
docker-compose.yaml
name: reprod

services:
  azurite:
    image: mcr.microsoft.com/azure-storage/azurite:3.32.0
    ports:
      - 10000:10000

  mytest:
    image: golang:1.23
    environment:
    - AZURITE_HOST=azurite
    volumes:
      - .:/ws
    working_dir: /ws
    entrypoint: ["go", "test", ".", "-v"]

Run

docker compose up azurite

then compare

go test . -v
docker compose run mytest

Anything we should know about your environment.

MacOS

Metadata

Metadata

Labels

ClientThis issue points to a problem in the data-plane of the library.Service AttentionWorkflow: This issue is responsible by Azure service team.StorageStorage Service (Queues, Blobs, Files)customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions