From 542409843cb898f2311c2f25f56c70d066ef3f0d Mon Sep 17 00:00:00 2001 From: LTLA Date: Sat, 13 Apr 2024 12:22:57 -0700 Subject: [PATCH] Prevent replay attacks on old request files from other users. We also purge each request file after processing to reduce the risk. --- main.go | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index e52e993..e9ea9d8 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "strconv" "io/fs" "syscall" + "sync" ) func dumpJsonResponse(w http.ResponseWriter, status int, v interface{}, path string) { @@ -33,18 +34,15 @@ func dumpJsonResponse(w http.ResponseWriter, status int, v interface{}, path str } } -func dumpErrorResponse(w http.ResponseWriter, status int, message string, path string) { - log.Printf("failed to process %q; %s\n", path, message) - dumpJsonResponse(w, status, map[string]interface{}{ "status": "ERROR", "reason": message }, path) -} - func dumpHttpErrorResponse(w http.ResponseWriter, err error, path string) { status_code := http.StatusInternalServerError var http_err *httpError if errors.As(err, &http_err) { status_code = http_err.Status } - dumpErrorResponse(w, status_code, err.Error(), path) + message := err.Error() + log.Printf("failed to process %q; %s\n", path, message) + dumpJsonResponse(w, status_code, map[string]interface{}{ "status": "ERROR", "reason": message }, path) } func checkRequestFile(path, staging string) (string, error) { @@ -107,6 +105,9 @@ func main() { } } + var mut sync.Mutex + in_action := map[string]bool{} + // Creating an endpoint to trigger jobs. http.HandleFunc("POST /new/{path}", func(w http.ResponseWriter, r *http.Request) { path := r.PathValue("path") @@ -118,6 +119,23 @@ func main() { return } + // Prevent replay attack of a currently-being-processed request file. + err = func() error { + mut.Lock() + defer mut.Unlock() + _, ok := in_action[path] + if !ok { + in_action[path] = true + return nil + } else { + return newHttpError(http.StatusBadRequest, errors.New("path is already being processed")) + } + }() + if err != nil { + dumpHttpErrorResponse(w, err, path) + return + } + var reportable_err error payload := map[string]interface{}{} reqtype := strings.TrimPrefix(path, "request-") @@ -160,10 +178,18 @@ func main() { } else if strings.HasPrefix(reqtype, "health_check-") { // TO-BE-DEPRECATED, see /check below. reportable_err = nil } else { - dumpErrorResponse(w, http.StatusBadRequest, "invalid request type", reqpath) - return + reportable_err = newHttpError(http.StatusBadRequest, errors.New("invalid request type")) } + // Purge the request file once it's processed, to reduce + // the potential for replay attacks. + func() { + mut.Lock() + defer mut.Unlock() + delete(in_action, path) + os.Remove(reqpath) + }() + if reportable_err == nil { payload["status"] = "SUCCESS" dumpJsonResponse(w, http.StatusOK, &payload, path)