| 48 | } |
| 49 | |
| 50 | func (h *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 51 | start := time.Now() |
| 52 | |
| 53 | // 0. Check protocol version. We reject rather than default so that an |
| 54 | // old client paired with a newer server (or vice versa) fails loudly |
| 55 | // instead of silently producing mismatched signatures. |
| 56 | version := r.Header.Get(sidecar.HeaderProxyVersion) |
| 57 | if version != sidecar.ProtocolV1 { |
| 58 | http.Error(w, "unsupported "+sidecar.HeaderProxyVersion+": "+version, http.StatusBadRequest) |
| 59 | return |
| 60 | } |
| 61 | |
| 62 | // 1. Verify timestamp |
| 63 | ts := r.Header.Get(sidecar.HeaderProxyTimestamp) |
| 64 | if ts == "" { |
| 65 | http.Error(w, "missing "+sidecar.HeaderProxyTimestamp, http.StatusBadRequest) |
| 66 | return |
| 67 | } |
| 68 | |
| 69 | // 2. Read body and verify SHA256 |
| 70 | body, err := io.ReadAll(r.Body) |
| 71 | if err != nil { |
| 72 | http.Error(w, "failed to read request body", http.StatusBadRequest) |
| 73 | return |
| 74 | } |
| 75 | r.Body.Close() |
| 76 | |
| 77 | claimedSHA := r.Header.Get(sidecar.HeaderBodySHA256) |
| 78 | if claimedSHA == "" { |
| 79 | http.Error(w, "missing "+sidecar.HeaderBodySHA256, http.StatusBadRequest) |
| 80 | return |
| 81 | } |
| 82 | actualSHA := sidecar.BodySHA256(body) |
| 83 | if claimedSHA != actualSHA { |
| 84 | http.Error(w, "body SHA256 mismatch", http.StatusBadRequest) |
| 85 | return |
| 86 | } |
| 87 | |
| 88 | // 3. Verify HMAC signature |
| 89 | //Enforce scheme=https and reject any path/query embedded in the target. |
| 90 | // The sandbox is untrusted: without this check it could send |
| 91 | // X-Lark-Proxy-Target: http://open.feishu.cn to force the injected real |
| 92 | // token out over cleartext HTTP, exposing it to any on-path attacker |
| 93 | // between the sidecar and upstream. |
| 94 | target := r.Header.Get(sidecar.HeaderProxyTarget) |
| 95 | if target == "" { |
| 96 | http.Error(w, "missing "+sidecar.HeaderProxyTarget, http.StatusBadRequest) |
| 97 | return |
| 98 | } |
| 99 | |
| 100 | pathAndQuery := r.URL.RequestURI() |
| 101 | targetHost, err := parseTarget(target) |
| 102 | if err != nil { |
| 103 | http.Error(w, "invalid "+sidecar.HeaderProxyTarget+": "+err.Error(), http.StatusForbidden) |
| 104 | h.logger.Printf("REJECT method=%s path=%s reason=%q", r.Method, sanitizePath(pathAndQuery), sanitizeError(err)) |
| 105 | return |
| 106 | } |
| 107 | |