diff --git a/injectproxy/routes.go b/injectproxy/routes.go index 266d3226..09a397ee 100644 --- a/injectproxy/routes.go +++ b/injectproxy/routes.go @@ -49,10 +49,12 @@ type routes struct { } type options struct { - enableLabelAPIs bool - passthroughPaths []string - errorOnReplace bool - registerer prometheus.Registerer + enableLabelAPIs bool + passthroughPaths []string + errorOnReplace bool + registerer prometheus.Registerer + extraHttpHeaders map[string]string + rewriteHostHeader string } type Option interface { @@ -96,6 +98,18 @@ func WithErrorOnReplace() Option { }) } +func WithExtraHttpHeader(key, value string) Option { + return optionFunc(func(o *options) { + o.extraHttpHeaders[key] = value + }) +} + +func WithRewriteHostHeader(host string) Option { + return optionFunc(func(o *options) { + o.rewriteHostHeader = host + }) +} + // mux abstracts away the behavior we expect from the http.ServeMux type in this package. type mux interface { http.Handler @@ -271,7 +285,19 @@ func NewRoutes(upstream *url.URL, label string, extractLabeler ExtractLabeler, o opt.registerer = prometheus.NewRegistry() } - proxy := httputil.NewSingleHostReverseProxy(upstream) + proxy := &httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(upstream) + if opt.rewriteHostHeader == "" { + r.Out.Host = r.In.Host + } else { + r.Out.Host = opt.rewriteHostHeader + } + for header, val := range opt.extraHttpHeaders { + r.Out.Header.Set(header, val) + } + }, + } r := &routes{ upstream: upstream, diff --git a/main.go b/main.go index 9c3520ff..c0d58a18 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,8 @@ func main() { enableLabelAPIs bool unsafePassthroughPaths string // Comma-delimited string. errorOnReplace bool + extraHttpHeaders arrayFlags + rewriteHostHeader string ) flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError) @@ -81,6 +83,8 @@ func main() { "This option is checked after Prometheus APIs, you cannot override enforced API endpoints to be not enforced with this option. Use carefully as it can easily cause a data leak if the provided path is an important "+ "API (like /api/v1/configuration) which isn't enforced by prom-label-proxy. NOTE: \"all\" matching paths like \"/\" or \"\" and regex are not allowed.") flagset.BoolVar(&errorOnReplace, "error-on-replace", false, "When specified, the proxy will return HTTP status code 400 if the query already contains a label matcher that differs from the one the proxy would inject.") + flagset.Var(&extraHttpHeaders, "extra-http-header", "HTTP header to add to the upstream query in the format 'header: value'. Can be repeated multiple times.") + flagset.StringVar(&rewriteHostHeader, "rewrite-host-header-to", "", "Rewrite host header to supplied value when sending the query to the upstream URL.") //nolint: errcheck // Parse() will exit on error. flagset.Parse(os.Args[1:]) @@ -126,6 +130,18 @@ func main() { opts = append(opts, injectproxy.WithErrorOnReplace()) } + for _, headerArg := range extraHttpHeaders { + header, val, found := strings.Cut(headerArg, ":") + if !found || len(strings.TrimSpace(header)) == 0 || len(strings.TrimSpace(val)) == 0 { + log.Fatalf("extra-http-header %s is not in the format 'key:value'", headerArg) + } + opts = append(opts, injectproxy.WithExtraHttpHeader(header, val)) + } + + if len(rewriteHostHeader) > 0 { + opts = append(opts, injectproxy.WithRewriteHostHeader(rewriteHostHeader)) + } + var extractLabeler injectproxy.ExtractLabeler switch { case len(labelValues) > 0: