mirror of
https://github.com/ferdzo/fs.git
synced 2026-06-04 05:06:46 +00:00
Harden S3 auth boundaries
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fs/metrics"
|
||||
"hash"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
@@ -55,6 +60,16 @@ func Middleware(
|
||||
return
|
||||
}
|
||||
|
||||
if err := wrapPayloadHashVerifier(r); err != nil {
|
||||
metrics.Default.ObserveAuth("error", "sigv4", authErrorClass(err))
|
||||
if onError != nil {
|
||||
onError(w, r, err)
|
||||
return
|
||||
}
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
metrics.Default.ObserveAuth("ok", resolvedCtx.AuthType, "none")
|
||||
if auditEnabled && logger != nil {
|
||||
requestID := middleware.GetReqID(r.Context())
|
||||
@@ -75,6 +90,65 @@ func Middleware(
|
||||
}
|
||||
}
|
||||
|
||||
func wrapPayloadHashVerifier(r *http.Request) error {
|
||||
if r == nil || r.Body == nil || r.Body == http.NoBody {
|
||||
return nil
|
||||
}
|
||||
payloadHash := resolvePayloadHash(r, false)
|
||||
if !payloadHashRequiresVerification(payloadHash) {
|
||||
return nil
|
||||
}
|
||||
if !isHexSHA256(payloadHash) {
|
||||
return ErrAuthorizationHeaderMalformed
|
||||
}
|
||||
expected, err := hex.DecodeString(strings.ToLower(payloadHash))
|
||||
if err != nil {
|
||||
return ErrAuthorizationHeaderMalformed
|
||||
}
|
||||
r.Body = &payloadHashVerifyingReadCloser{
|
||||
inner: r.Body,
|
||||
hasher: sha256.New(),
|
||||
expected: expected,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type payloadHashVerifyingReadCloser struct {
|
||||
inner io.ReadCloser
|
||||
hasher hash.Hash
|
||||
expected []byte
|
||||
done bool
|
||||
}
|
||||
|
||||
func (r *payloadHashVerifyingReadCloser) Read(p []byte) (int, error) {
|
||||
n, err := r.inner.Read(p)
|
||||
if n > 0 {
|
||||
_, _ = r.hasher.Write(p[:n])
|
||||
}
|
||||
if err == io.EOF && !r.done {
|
||||
r.done = true
|
||||
if !equalBytes(r.hasher.Sum(nil), r.expected) {
|
||||
return n, ErrSignatureDoesNotMatch
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *payloadHashVerifyingReadCloser) Close() error {
|
||||
return r.inner.Close()
|
||||
}
|
||||
|
||||
func equalBytes(left, right []byte) bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
var diff byte
|
||||
for i := range left {
|
||||
diff |= left[i] ^ right[i]
|
||||
}
|
||||
return diff == 0
|
||||
}
|
||||
|
||||
func authErrorClass(err error) string {
|
||||
switch {
|
||||
case errors.Is(err, ErrInvalidAccessKeyID):
|
||||
|
||||
Reference in New Issue
Block a user