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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,11 +37,16 @@ func sha256Hex(data []byte) string {
|
|||||||
return hexLower(s)
|
return hexLower(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hmacSha256Hex returns hex(HMAC-SHA256(key, data)).
|
// hmacSha256 returns the raw 32-byte HMAC-SHA256(key, data).
|
||||||
func hmacSha256Hex(key, data []byte) string {
|
func hmacSha256(key, data []byte) []byte {
|
||||||
h := hmac.New(sha256.New, key)
|
h := hmac.New(sha256.New, key)
|
||||||
h.Write(data)
|
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.
|
// SignedGet is the prepared request.
|
||||||
@@ -58,8 +63,8 @@ func signGet(endpoint, bucket, key, secret, name string, now time.Time) SignedGe
|
|||||||
const service = "s3"
|
const service = "s3"
|
||||||
|
|
||||||
utc := now.UTC()
|
utc := now.UTC()
|
||||||
date := utc.Format("%Y%m%d")
|
date := utc.Format("2006") + utc.Format("01") + utc.Format("02")
|
||||||
tstamp := utc.Format("%H%M%S")
|
tstamp := utc.Format("15") + utc.Format("04") + utc.Format("05")
|
||||||
amzDate := date + "T" + tstamp + "Z"
|
amzDate := date + "T" + tstamp + "Z"
|
||||||
|
|
||||||
host := endpoint
|
host := endpoint
|
||||||
@@ -71,11 +76,12 @@ func signGet(endpoint, bucket, key, secret, name string, now time.Time) SignedGe
|
|||||||
|
|
||||||
canonicalPath := "/" + bucket + "/" + name
|
canonicalPath := "/" + bucket + "/" + name
|
||||||
canonicalQuery := ""
|
canonicalQuery := ""
|
||||||
|
payloadHash := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // sha256("")
|
||||||
canonicalHeaders := "host:" + host + "\n" +
|
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"
|
"x-amz-date:" + amzDate + "\n"
|
||||||
signedHeaders := "host;x-amz-content-sha256;x-amz-date"
|
signedHeaders := "host;x-amz-checksum-mode;x-amz-content-sha256;x-amz-date"
|
||||||
payloadHash := "UNSIGNED-PAYLOAD"
|
|
||||||
|
|
||||||
canonicalRequest := strings.Join([]string{
|
canonicalRequest := strings.Join([]string{
|
||||||
"GET",
|
"GET",
|
||||||
@@ -94,11 +100,11 @@ func signGet(endpoint, bucket, key, secret, name string, now time.Time) SignedGe
|
|||||||
sha256Hex([]byte(canonicalRequest)),
|
sha256Hex([]byte(canonicalRequest)),
|
||||||
}, "\n")
|
}, "\n")
|
||||||
|
|
||||||
kDate := hmacSha256Hex([]byte("AWS4" + secret), []byte(date))
|
kDate := hmacSha256([]byte("AWS4" + secret), []byte(date))
|
||||||
kRegion := hmacSha256Hex([]byte(kDate), []byte(region))
|
kRegion := hmacSha256(kDate, []byte(region))
|
||||||
kService := hmacSha256Hex([]byte(kRegion), []byte(service))
|
kService := hmacSha256(kRegion, []byte(service))
|
||||||
kSigning := hmacSha256Hex([]byte(kService), []byte("aws4_request"))
|
kSigning := hmacSha256(kService, []byte("aws4_request"))
|
||||||
signature := hmacSha256Hex([]byte(kSigning), []byte(stringToSign))
|
signature := hmacSha256Hex(kSigning, []byte(stringToSign))
|
||||||
|
|
||||||
auth := "AWS4-HMAC-SHA256 Credential=" + key + "/" + scope +
|
auth := "AWS4-HMAC-SHA256 Credential=" + key + "/" + scope +
|
||||||
", SignedHeaders=" + signedHeaders + ", Signature=" + signature
|
", 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("Authorization", sg.Auth)
|
||||||
req.Header.Set("x-amz-date", sg.Date)
|
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)
|
req.Header.Set("Host", sg.Host)
|
||||||
|
|
||||||
resp, rerr := c.Client.Do(req)
|
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 != "" {
|
if ct := resp.Header.Get("Content-Type"); ct != "" {
|
||||||
w.Header().Set("Content-Type", ct)
|
w.Header().Set("Content-Type", ct)
|
||||||
}
|
}
|
||||||
if cl := resp.Header.Get("Content-Length"); cl != "" {
|
// read the whole object then write once (small media; keeps Content-Length exact)
|
||||||
w.Header().Set("Content-Length", cl)
|
all, berr := io.ReadAll(resp.Body)
|
||||||
}
|
if berr == nil {
|
||||||
var buf bytes.Buffer
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(all)))
|
||||||
for {
|
_, _ = w.Write(all)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
|
|||||||
Reference in New Issue
Block a user