From df4a19933c545232fbc0476e12468200f9762c83 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 9 Sep 2026 18:25:34 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20/media=20proxy=20to=20Garage=20S3=20via?= =?UTF-8?q?=20AWS=20SigV4=20=E2=80=94=20images=20now=20serve=20publicly;?= =?UTF-8?q?=20remove=20debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/media.go | 56 +++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/app/src/media.go b/app/src/media.go index 08e7159..28b8227 100644 --- a/app/src/media.go +++ b/app/src/media.go @@ -5,10 +5,10 @@ package main import ( - "bytes" "crypto/hmac" "crypto/sha256" "fmt" + "io" "net/http" "strings" "time" @@ -37,11 +37,16 @@ func sha256Hex(data []byte) string { return hexLower(s) } -// hmacSha256Hex returns hex(HMAC-SHA256(key, data)). -func hmacSha256Hex(key, data []byte) string { +// hmacSha256 returns the raw 32-byte HMAC-SHA256(key, data). +func hmacSha256(key, data []byte) []byte { h := hmac.New(sha256.New, key) h.Write(data) - return hexLower(h.Sum(nil)) + return h.Sum(nil) +} + +// hmacSha256Hex returns hex(HMAC-SHA256(key, data)). +func hmacSha256Hex(key, data []byte) string { + return hexLower(hmacSha256(key, data)) } // SignedGet is the prepared request. @@ -58,8 +63,8 @@ func signGet(endpoint, bucket, key, secret, name string, now time.Time) SignedGe const service = "s3" utc := now.UTC() - date := utc.Format("%Y%m%d") - tstamp := utc.Format("%H%M%S") + date := utc.Format("2006") + utc.Format("01") + utc.Format("02") + tstamp := utc.Format("15") + utc.Format("04") + utc.Format("05") amzDate := date + "T" + tstamp + "Z" host := endpoint @@ -71,11 +76,12 @@ func signGet(endpoint, bucket, key, secret, name string, now time.Time) SignedGe canonicalPath := "/" + bucket + "/" + name canonicalQuery := "" + payloadHash := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // sha256("") canonicalHeaders := "host:" + host + "\n" + - "x-amz-content-sha256:UNSIGNED-PAYLOAD\n" + + "x-amz-checksum-mode:ENABLED\n" + + "x-amz-content-sha256:" + payloadHash + "\n" + "x-amz-date:" + amzDate + "\n" - signedHeaders := "host;x-amz-content-sha256;x-amz-date" - payloadHash := "UNSIGNED-PAYLOAD" + signedHeaders := "host;x-amz-checksum-mode;x-amz-content-sha256;x-amz-date" canonicalRequest := strings.Join([]string{ "GET", @@ -94,11 +100,11 @@ func signGet(endpoint, bucket, key, secret, name string, now time.Time) SignedGe sha256Hex([]byte(canonicalRequest)), }, "\n") - kDate := hmacSha256Hex([]byte("AWS4" + secret), []byte(date)) - kRegion := hmacSha256Hex([]byte(kDate), []byte(region)) - kService := hmacSha256Hex([]byte(kRegion), []byte(service)) - kSigning := hmacSha256Hex([]byte(kService), []byte("aws4_request")) - signature := hmacSha256Hex([]byte(kSigning), []byte(stringToSign)) + kDate := hmacSha256([]byte("AWS4" + secret), []byte(date)) + kRegion := hmacSha256(kDate, []byte(region)) + kService := hmacSha256(kRegion, []byte(service)) + kSigning := hmacSha256(kService, []byte("aws4_request")) + signature := hmacSha256Hex(kSigning, []byte(stringToSign)) auth := "AWS4-HMAC-SHA256 Credential=" + key + "/" + scope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature @@ -124,7 +130,8 @@ func (c *S3MediaClient) Get(name string, w http.ResponseWriter) int { } req.Header.Set("Authorization", sg.Auth) req.Header.Set("x-amz-date", sg.Date) - req.Header.Set("x-amz-content-sha256", "UNSIGNED-PAYLOAD") + req.Header.Set("x-amz-content-sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + req.Header.Set("x-amz-checksum-mode", "ENABLED") req.Header.Set("Host", sg.Host) resp, rerr := c.Client.Do(req) @@ -137,20 +144,11 @@ func (c *S3MediaClient) Get(name string, w http.ResponseWriter) int { if ct := resp.Header.Get("Content-Type"); ct != "" { w.Header().Set("Content-Type", ct) } - if cl := resp.Header.Get("Content-Length"); cl != "" { - w.Header().Set("Content-Length", cl) - } - var buf bytes.Buffer - for { - n, be := resp.Body.Read(buf.AvailableBuffer()) - if n <= 0 || be != nil { - break - } - data := buf.Bytes() - if _, we := w.Write(data); we != nil { - break - } - buf.Reset() + // read the whole object then write once (small media; keeps Content-Length exact) + all, berr := io.ReadAll(resp.Body) + if berr == nil { + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(all))) + _, _ = w.Write(all) } } return status