mirror of
https://github.com/gopasspw/gopass.git
synced 2026-05-30 11:18:48 +02:00
32d87a8d3d
* Add a dockerfile to debug debian issues Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Add simple http proxy to aid in debugging failing requests Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Add debug instructions Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Move debug helpers out of the way Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> --------- Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
148 lines
5.9 KiB
Go
148 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
listen = flag.String("listen", ":8080", "Address to listen on")
|
|
// List of common text-based MIME types
|
|
textContentTypes = map[string]bool{
|
|
"text/plain": true,
|
|
"text/html": true,
|
|
"text/css": true,
|
|
"text/xml": true,
|
|
"text/csv": true,
|
|
"text/javascript": true,
|
|
"text/markdown": true,
|
|
"application/json": true,
|
|
"application/xml": true,
|
|
"application/ld+json": true,
|
|
"application/javascript": true, // RFC 4329, though text/javascript is more common
|
|
"application/xhtml+xml": true,
|
|
"application/atom+xml": true,
|
|
"application/rss+xml": true,
|
|
"image/svg+xml": true, // SVG is XML-based and human-readable
|
|
}
|
|
)
|
|
|
|
// isClientOrServerError checks if the status code is a 4xx or 5xx error.
|
|
func isClientOrServerError(statusCode int) bool {
|
|
return statusCode >= 400 && statusCode <= 599
|
|
}
|
|
|
|
// isTextContentType checks if the given Content-Type string indicates a textual format.
|
|
func isTextContentType(contentTypeHeader string) bool {
|
|
// Normalize: to lowercase and take only the main type/subtype, ignore parameters like charset
|
|
mediaType := strings.ToLower(strings.Split(contentTypeHeader, ";")[0])
|
|
|
|
// Check if the media type is in the predefined list of text content types
|
|
if textContentTypes[mediaType] {
|
|
return true
|
|
}
|
|
|
|
// Catch-all for other "text/*" types
|
|
if strings.HasPrefix(mediaType, "text/") {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func modifyResponse(resp *http.Response) error {
|
|
log.Printf("Response from upstream for %s: Status %s\n", resp.Request.URL, resp.Status)
|
|
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if isClientOrServerError(resp.StatusCode) || isTextContentType(contentType) {
|
|
statusStr := "SUCCESSFUL"
|
|
if resp.StatusCode >= 400 {
|
|
statusStr = "FAILED"
|
|
}
|
|
log.Printf("Upstream request to %s %s with status: %s. Dumping request and response.\n", resp.Request.URL, statusStr, resp.Status)
|
|
dump, err := httputil.DumpRequestOut(resp.Request, true) // true to include body
|
|
if err != nil {
|
|
log.Printf("Error dumping %s request for %s: %v\n", statusStr, resp.Request.URL, err)
|
|
// Fallback logging if dump fails
|
|
fmt.Fprintf(os.Stdout, "---- BEGIN %s UPSTREAM REQUEST (DUMP FAILED: %v) ----\nURL: %s\nMethod: %s\nHeaders: %v\n---- END %s UPSTREAM REQUEST ----\n", statusStr, err, resp.Request.URL, resp.Request.Method, resp.Request.Header, statusStr)
|
|
return nil // Allow the original error response to proceed if dump fails
|
|
}
|
|
fmt.Fprintf(os.Stdout, "---- BEGIN %s UPSTREAM REQUEST DUMP (URL: %s) ----\n%s\n---- END %s UPSTREAM REQUEST DUMP ----\n", statusStr, resp.Request.URL, string(dump), statusStr)
|
|
// DumpResponse reads and replaces resp.Body with a new ReadCloser.
|
|
dump, err = httputil.DumpResponse(resp, true) // true to include body
|
|
if err != nil {
|
|
log.Printf("Error dumping %s response for %s: %v\n", statusStr, resp.Request.URL, err)
|
|
// Fallback logging if dump fails
|
|
fmt.Fprintf(os.Stdout, "---- BEGIN %s UPSTREAM RESPONSE (DUMP FAILED: %v) ----\nURL: %s\nStatus: %s\n---- END %s UPSTREAM RESPONSE ----\n", statusStr, err, resp.Request.URL, resp.Status, statusStr)
|
|
return nil // Allow the original error response to proceed if dump fails
|
|
}
|
|
fmt.Fprintf(os.Stdout, "---- BEGIN %s UPSTREAM RESPONSE DUMP (URL: %s) ----\n%s\n---- END %s UPSTREAM RESPONSE DUMP ----\n", statusStr, resp.Request.URL, string(dump), statusStr)
|
|
// The body is already replaced by DumpResponse, so ReverseProxy can send it.
|
|
} else {
|
|
// For other statuses (1xx informational, 3xx redirection), just log.
|
|
// Redirections are typically handled by the client (browser) based on the headers.
|
|
// ReverseProxy will pass these responses through.
|
|
log.Printf("Response from %s with status %s (not dumping content for this type).\n", resp.Request.URL, resp.Status)
|
|
}
|
|
|
|
return nil // No error modifying response, let ReverseProxy handle it
|
|
}
|
|
|
|
func main() {
|
|
director := func(req *http.Request) {
|
|
// httputil.ReverseProxy will automatically set X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto.
|
|
// It uses req.URL.Host as the destination.
|
|
// We need to ensure req.URL.Scheme and req.URL.Host are correctly set.
|
|
// if req.URL.Scheme == "" {
|
|
// req.URL.Scheme = "http" // Assume http if not specified
|
|
// }
|
|
req.URL.Scheme = "https" // Force HTTPS
|
|
// The Host header is already set by the client to the target server.
|
|
// ReverseProxy uses req.URL.Host for dialing.
|
|
// Ensure req.URL.Host is set to the target. It usually is from r.RequestURI.
|
|
log.Printf("Proxying %s request for: %s\n", req.Method, req.URL.String())
|
|
}
|
|
|
|
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
|
|
log.Printf("Proxy error for %s: %v\n", r.URL, err)
|
|
// This error means the upstream was unreachable (e.g., connection refused, DNS lookup failure).
|
|
// There is no HTTP response from upstream to dump.
|
|
fmt.Fprintf(os.Stdout, "---- FAILED UPSTREAM ATTEMPT ----\nURL: %s\nError: %v\n---- END FAILED UPSTREAM ATTEMPT ----\n", r.URL, err)
|
|
http.Error(w, fmt.Sprintf("Upstream server error: %v", err), http.StatusBadGateway)
|
|
}
|
|
|
|
reverseProxy := &httputil.ReverseProxy{
|
|
Director: director,
|
|
ModifyResponse: modifyResponse,
|
|
ErrorHandler: errorHandler,
|
|
Transport: http.DefaultTransport, // You can customize transport (e.g., for timeouts)
|
|
}
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodConnect {
|
|
http.Error(w, "CONNECT method not supported", http.StatusMethodNotAllowed)
|
|
return
|
|
} else {
|
|
reverseProxy.ServeHTTP(w, r)
|
|
}
|
|
})
|
|
|
|
log.Printf("Starting HTTP proxy on %s\n", *listen)
|
|
server := &http.Server{
|
|
Addr: *listen,
|
|
Handler: handler,
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
}
|
|
|
|
if err := server.ListenAndServe(); err != nil {
|
|
log.Fatalf("Failed to start proxy server: %v\n", err)
|
|
}
|
|
}
|