[misc] Add upload URL signing and rate limiting (#7502)

This commit is contained in:
Bethuel Mmbaga
2026-09-24 17:34:58 +03:00
committed by GitHub
parent 94106b57ab
commit 0a128cea6f
11 changed files with 488 additions and 47 deletions
+34 -17
View File
@@ -8,9 +8,12 @@ import (
"os"
"path/filepath"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/http/middleware"
"github.com/netbirdio/netbird/upload-server/types"
)
@@ -20,11 +23,12 @@ const (
)
type local struct {
url string
dir string
url string
dir string
signer *signer
}
func configureLocalHandlers(mux *http.ServeMux) error {
func configureLocalHandlers(mux *http.ServeMux, limiter *middleware.APIRateLimiter) error {
envURL, ok := os.LookupEnv("SERVER_URL")
if !ok {
return fmt.Errorf("SERVER_URL environment variable is required")
@@ -44,11 +48,17 @@ func configureLocalHandlers(mux *http.ServeMux) error {
dir = envDir
}
l := &local{
url: envURL,
dir: dir,
uploadSigner, err := newSigner()
if err != nil {
return err
}
mux.HandleFunc(types.GetURLPath, l.handlerGetUploadURL)
l := &local{
url: envURL,
dir: dir,
signer: uploadSigner,
}
mux.Handle(types.GetURLPath, limiter.Middleware(http.HandlerFunc(l.handlerGetUploadURL)))
mux.HandleFunc(putURLPath+putHandler, l.handlePutRequest)
return nil
@@ -80,10 +90,11 @@ func (l *local) getUploadURL(objectKey string) (string, error) {
return "", fmt.Errorf("failed to parse upload URL: %w", err)
}
newURL := parsedUploadURL.JoinPath(parsedUploadURL.Path, putURLPath, objectKey)
newURL.RawQuery = l.signer.sign(objectKey, time.Now()).Encode()
return newURL.String(), nil
}
const maxUploadSize = 150 << 20
const maxUploadSize = 50 << 20
func (l *local) handlePutRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
@@ -91,13 +102,6 @@ func (l *local) handlePutRequest(w http.ResponseWriter, r *http.Request) {
return
}
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "request body too large or failed to read", http.StatusRequestEntityTooLarge)
return
}
uploadDir := r.PathValue("dir")
if uploadDir == "" {
http.Error(w, "missing dir path", http.StatusBadRequest)
@@ -109,6 +113,19 @@ func (l *local) handlePutRequest(w http.ResponseWriter, r *http.Request) {
return
}
if err := l.signer.verify(uploadDir+"/"+uploadFile, r.URL.Query(), time.Now()); err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
log.Warnf("Rejected upload of %s/%s: %v", uploadDir, uploadFile, err)
return
}
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "request body too large or failed to read", http.StatusRequestEntityTooLarge)
return
}
cleanBase := filepath.Clean(l.dir) + string(filepath.Separator)
dirPath := filepath.Clean(filepath.Join(l.dir, uploadDir))
@@ -125,14 +142,14 @@ func (l *local) handlePutRequest(w http.ResponseWriter, r *http.Request) {
return
}
if err = os.MkdirAll(dirPath, 0750); err != nil {
if err = os.MkdirAll(dirPath, 0o750); err != nil {
http.Error(w, "failed to create upload dir", http.StatusInternalServerError)
log.Errorf("Failed to create upload dir: %v", err)
return
}
flags := os.O_WRONLY | os.O_CREATE | os.O_EXCL
f, err := os.OpenFile(filePath, flags, 0600)
f, err := os.OpenFile(filePath, flags, 0o600)
if err != nil {
if os.IsExist(err) {
http.Error(w, "file already exists", http.StatusConflict)