(w http.ResponseWriter, r *http.Request, host string)
| 106 | } |
| 107 | |
| 108 | func (d *piiDispatcher) serve(w http.ResponseWriter, r *http.Request, host string) { |
| 109 | start := time.Now() |
| 110 | cw := &countingResponseWriter{ResponseWriter: w} |
| 111 | w = cw |
| 112 | |
| 113 | var ( |
| 114 | correlationID string |
| 115 | bytesSent int64 |
| 116 | ) |
| 117 | defer func() { |
| 118 | d.recordTrafficEvent(host, correlationID, bytesSent, cw.bytes, cw.status, start) |
| 119 | }() |
| 120 | |
| 121 | body, err := io.ReadAll(r.Body) |
| 122 | if err != nil { |
| 123 | http.Error(w, "mitm: read body: "+err.Error(), http.StatusBadGateway) |
| 124 | return |
| 125 | } |
| 126 | _ = r.Body.Close() |
| 127 | |
| 128 | correlationID = r.Header.Get(d.corrHeader) |
| 129 | if correlationID == "" { |
| 130 | correlationID = r.Header.Get("x-request-id") |
| 131 | } |
| 132 | |
| 133 | shape := classifyRequestShape(host, r.URL.Path) |
| 134 | cfgs := d.detectorsByHost[strings.ToLower(host)] |
| 135 | if len(cfgs) > 0 && shape != shapeUnknown { |
| 136 | redacted, blocked, err := d.redactRequest(r.Context(), body, shape, cfgs, correlationID) |
| 137 | switch { |
| 138 | case err != nil: |
| 139 | // Fail closed: a detector outage must not silently forward the |
| 140 | // request unredacted — the operator configured this host's |
| 141 | // model with detectors precisely to catch this PII. |
| 142 | xlog.Error("mitm: NER redaction failed; blocking request (fail-closed)", "host", host, "path", r.URL.Path, "error", err) |
| 143 | writePIIBlocked(w, correlationID) |
| 144 | return |
| 145 | case blocked: |
| 146 | writePIIBlocked(w, correlationID) |
| 147 | return |
| 148 | default: |
| 149 | body = redacted |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | upstreamURL := "https://" + d.dialHost(host) + r.URL.RequestURI() |
| 154 | upstreamReq, err := http.NewRequestWithContext(r.Context(), r.Method, upstreamURL, bytes.NewReader(body)) |
| 155 | if err != nil { |
| 156 | http.Error(w, "mitm: build upstream request: "+err.Error(), http.StatusBadGateway) |
| 157 | return |
| 158 | } |
| 159 | upstreamReq.Header = cloneHopByHopFiltered(r.Header) |
| 160 | upstreamReq.ContentLength = int64(len(body)) |
| 161 | upstreamReq.Header.Set("Content-Length", fmt.Sprintf("%d", len(body))) |
| 162 | bytesSent = int64(len(body)) |
| 163 | |
| 164 | resp, err := d.client.Do(upstreamReq) |
| 165 | if err != nil { |
nothing calls this directly
no test coverage detected