feat: /media proxy to Garage S3 via AWS SigV4 — images now serve publicly; remove debug

This commit is contained in:
sam
2026-09-09 18:25:34 +10:00
parent 5bec37a8a1
commit df4a19933c

View File

@@ -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