feat: /media proxy to Garage S3 via AWS SigV4 — images now serve publicly; remove debug
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user