Search code examples
amazon-web-servicesgoamazon-s3supabase

Golang AWS SDK v2 SignatureDoesNotMatch error when using S3 protocol with Supabase storage


Background

I am trying to interact with Supabase's storage using the S3 protocol in Golang. I have followed Supabase's example provided in JavaScript and translated it into Go, but I keep encountering a SignatureDoesNotMatch error.

My Setup

I've created a bucket named uploads. I created a new access keys in my supabase project settings (access key and secret key). I put those in my .env file named AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

My Code

This is the code that i wrote for interacting with supabase's storage using the s3 protocol.

package providers

import (
    "context"
    "io"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

type S3ProviderImpl interface {
    Upload(fileName string, data io.ReadCloser, bucket string) (string, error)
}

type StorageProvider struct {
    s3cl *s3.Client
}

func NewStorageProvider() *StorageProvider {
    endpoint := "https://sldogsqddfisyaxbeujt.supabase.co/storage/v1/s3"

    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("ap-southeast-1"))
    if err != nil {
        log.Printf("error: %v", err)
        return nil
    }

    client := s3.NewFromConfig(cfg, func(o *s3.Options) {
        o.BaseEndpoint = aws.String(endpoint)
        o.UsePathStyle = true
    })

    return &StorageProvider{
        s3cl: client,
    }
}

// This is a test function
func (provider *StorageProvider) List() {
    res, err := provider.s3cl.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
    if err != nil {
        log.Println(err)
    }
    for _, object := range res.Buckets {
        log.Printf("key=%s ", aws.ToString(object.Name))
    }

}

In the main.go file i created a new StorageProvider and called the List function.

package main

import (
    "amelia/internal/providers"
    "log"

    "github.com/joho/godotenv"
    _ "github.com/lib/pq"
    "github.com/rs/zerolog"
)

func startup() {
    log.Println("Loading .env")
    err := godotenv.Load(".env")
    if err != nil {
        log.Panic(err)
    }
}

func main() {
    startup()
    storage := providers.NewStorageProvider()
    storage.List()
}

My Reference

Since supabase only provide instructions on how to use s3 protocol only in javascript I figured to translate it in go. In their examples they wrote this.

import { S3Client } from '@aws-sdk/client-s3';

const client = new S3Client({
  forcePathStyle: true,
  region: 'project_region',
  endpoint: 'https://project_ref.supabase.co/storage/v1/s3',
  credentials: {
    accessKeyId: 'your_access_key_id',
    accessSecretKey: 'your_secret_access_key',
  }
})

What I've tried

I read the AWS SDK v2 for go and did tried to do the same. But i always endup with this error.

024/05/21 18:27:53 operation error S3: ListBuckets, https response error StatusCode: 403, RequestID: , HostID: , api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.

So i tried to interact with it using the aws cli. It worked. I've set everything the same, the access and secret key, location and the endpoint.

PS C:\Users\josti> aws s3 ls uploads --endpoint=https://sldogsqddfisyaxbeujt.supabase.co/storage/v1/s3
2024-05-20 16:49:24       8269 abc.jpg

I tried to follow other's suggestion such as adding / at the end of the bucket name, verifying my keys, added ./ or / before the file name, but nothing seems to work.

My Environment

OS: Windows 11 24h2
Go: 1.22
AWS SDK version:

github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 

Edit: Included the main.go file for clarification.


Solution

  • For anyone else who spends way too much time on this like I did; I had success just using the v4 request signer (github.com/aws/aws-sdk-go-v2/aws/signer/v4) and calling s3 myself like below:

    req, err := http.NewRequest("GET", "{supabaseurl}/{bucketname}/{key}", nil)
    
    if err != nil {
        return "", err
    }
    
    creds := aws.Credentials{
        AccessKeyID:    "supabase-s3-access-key",
        SecretAccessKey: "supabase-s3-secret-key",
    }
    
    err = signer.SignHTTP(context.Background(),
        creds,
        req,
        // empty string hash, no payload
        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "s3",
        "region",
        time.Now(),
    )
    
    if err != nil {
        return "", err
    }
    
    client := &http.Client{}
    
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()