package main import ( "fmt" "io" "net/http" "net/url" "time" ) // proxyThroughWAF forwards the request to the WAF, returns the response. // The WAF is expected to either block (403) or forward to the backend (2xx). func proxyThroughWAF(w http.ResponseWriter, r *http.Request) (*http.Response, error) { cfg := getConfig() target, err := url.Parse(cfg.WAFURL) if err != nil { return nil, fmt.Errorf("invalid WAF URL: %w", err) } // Clone the request for the WAF. proxyReq, err := http.NewRequestWithContext(r.Context(), r.Method, target.String()+r.URL.Path, r.Body) if err != nil { return nil, fmt.Errorf("create proxy request: %w", err) } // Copy headers. for key, vals := range r.Header { for _, v := range vals { proxyReq.Header.Add(key, v) } } proxyReq.Header.Set("X-Forwarded-For", r.RemoteAddr) proxyReq.Header.Set("X-Real-IP", r.RemoteAddr) client := &http.Client{Timeout: time.Duration(cfg.TimeoutMs) * time.Millisecond} resp, err := client.Do(proxyReq) if err != nil { return nil, fmt.Errorf("WAF unreachable: %w", err) } return resp, nil } // bypassToBackend forwards the request directly to the backend (fail-open path). func bypassToBackend(w http.ResponseWriter, r *http.Request) { cfg := getConfig() target, err := url.Parse(cfg.BackendURL) if err != nil { http.Error(w, "backend unavailable", http.StatusServiceUnavailable) return } proxyReq, err := http.NewRequestWithContext(r.Context(), r.Method, target.String()+r.URL.Path, r.Body) if err != nil { http.Error(w, "proxy error", http.StatusInternalServerError) return } for key, vals := range r.Header { for _, v := range vals { proxyReq.Header.Add(key, v) } } client := &http.Client{Timeout: time.Duration(cfg.TimeoutMs) * time.Millisecond} resp, err := client.Do(proxyReq) if err != nil { http.Error(w, "backend unreachable", http.StatusBadGateway) return } defer resp.Body.Close() // Copy response back. for key, vals := range resp.Header { for _, v := range vals { w.Header().Add(key, v) } } w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) }