client/web: check content-type on PATCH requests

Updates #10261
Fixes tailscale/corp#16267

Signed-off-by: Will Norris <will@tailscale.com>
pull/10543/head
Will Norris 12 months ago committed by Will Norris
parent c615fe2296
commit 69b56462fc

@ -965,6 +965,13 @@ func (s *Server) proxyRequestToLocalAPI(w http.ResponseWriter, r *http.Request)
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
if r.Method == httpm.PATCH {
// enforce that PATCH requests are always application/json
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
}
if !slices.Contains(localapiAllowlist, path) {
http.Error(w, fmt.Sprintf("%s not allowed from localapi proxy", path), http.StatusForbidden)
return

@ -101,28 +101,43 @@ func TestServeAPI(t *testing.T) {
tests := []struct {
name string
reqMethod string
reqPath string
reqContentType string
wantResp string
wantStatus int
}{{
name: "invalid_endpoint",
reqMethod: httpm.POST,
reqPath: "/not-an-endpoint",
wantResp: "invalid endpoint",
wantStatus: http.StatusNotFound,
}, {
name: "not_in_localapi_allowlist",
reqMethod: httpm.POST,
reqPath: "/local/v0/not-allowlisted",
wantResp: "/v0/not-allowlisted not allowed from localapi proxy",
wantStatus: http.StatusForbidden,
}, {
name: "in_localapi_allowlist",
reqMethod: httpm.POST,
reqPath: "/local/v0/logout",
wantResp: "success", // Successfully allowed to hit localapi.
wantStatus: http.StatusOK,
}, {
name: "patch_bad_contenttype",
reqMethod: httpm.PATCH,
reqPath: "/local/v0/prefs",
reqContentType: "multipart/form-data",
wantResp: "invalid request",
wantStatus: http.StatusBadRequest,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := httptest.NewRequest("POST", "/api"+tt.reqPath, nil)
r := httptest.NewRequest(tt.reqMethod, "/api"+tt.reqPath, nil)
if tt.reqContentType != "" {
r.Header.Add("Content-Type", tt.reqContentType)
}
w := httptest.NewRecorder()
s.serveAPI(w, r)

Loading…
Cancel
Save