add ability to drop incoming headers #108

In some cases proxy should sanitize incoming headers. --drop-header and $DROP_HEADERS set list of headers (keys) and those headers removed from the request.
This commit is contained in:
Umputun 2021-09-11 14:38:56 -05:00
parent f908fa6fe5
commit 76fa56777f
7 changed files with 42 additions and 23 deletions

View File

@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: set up go 1.16
- name: set up go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
id: go
- name: checkout

View File

@ -18,7 +18,7 @@ Reproxy is a simple edge HTTP(s) server / reverse proxy supporting various provi
- Docker container distribution
- Built-in static assets server with optional "SPA friendly" mode
- Support for redirect rules
- Optional limiter for the overall activity as well as for user's activity
- Optional limiter for the overall activity as well as for user's activity
- Live health check and fail-over/load-balancing
- Management server with routes info and prometheus metrics
- Plugins support via RPC to implement custom functionality
@ -225,6 +225,7 @@ supported codes:
- `--gzip` enables gzip compression for responses.
- `--max=N` allows to set the maximum size of request (default 64k). Setting it to `0` disables the size check.
- `--header` sets extra header(s) added to each proxied response.
- `--drop-header` drops headers from incoming request.
For example this is how it can be done with the docker compose:
@ -324,7 +325,8 @@ This is the list of all options supporting multiple elements:
-l, --listen= listen on host:port (default: 0.0.0.0:8080/8443 under docker, 127.0.0.1:80/443 without) [$LISTEN]
-m, --max= max request size (default: 64K) [$MAX_SIZE]
-g, --gzip enable gz compression [$GZIP]
-x, --header= proxy headers
-x, --header= outgoing proxy headers to add
--drop-header= incoming headers to drop [$DROP_HEADERS]
--lb-type=[random|failover] load balancer type (default: random) [$LB_TYPE]
--signature enable reproxy signature headers [$SIGNATURE]
--dbg debug mode [$DEBUG]
@ -407,6 +409,7 @@ plugin:
Help Options:
-h, --help Show this help message
```
## Status

View File

@ -33,7 +33,8 @@ var opts struct {
Listen string `short:"l" long:"listen" env:"LISTEN" description:"listen on host:port (default: 0.0.0.0:8080/8443 under docker, 127.0.0.1:80/443 without)"`
MaxSize string `short:"m" long:"max" env:"MAX_SIZE" default:"64K" description:"max request size"`
GzipEnabled bool `short:"g" long:"gzip" env:"GZIP" description:"enable gz compression"`
ProxyHeaders []string `short:"x" long:"header" description:"proxy headers"` // env HEADER split in code to allow , inside ""
ProxyHeaders []string `short:"x" long:"header" description:"outgoing proxy headers to add"` // env HEADER split in code to allow , inside ""
DropHeaders []string `long:"drop-header" env:"DROP_HEADERS" description:"incoming headers to drop" env-delim:","`
LBType string `long:"lb-type" env:"LB_TYPE" description:"load balancer type" choice:"random" choice:"failover" default:"random"` //nolint
@ -239,6 +240,7 @@ func run() error {
GzEnabled: opts.GzipEnabled,
SSLConfig: sslConfig,
ProxyHeaders: proxyHeaders,
DropHeader: opts.DropHeaders,
AccessLog: accessLog,
StdOutEnabled: opts.Logger.StdOut,
Signature: opts.Signature,
@ -258,7 +260,7 @@ func run() error {
Reporter: errReporter,
PluginConductor: makePluginConductor(ctx),
ThrottleSystem: opts.Throttle.System * 3,
ThottleUser: opts.Throttle.User,
ThrottleUser: opts.Throttle.User,
}
err = px.Run(ctx)

View File

@ -45,7 +45,7 @@ func NewMetrics() *Metrics {
Buckets: []float64{0.01, 0.1, 0.5, 1, 2, 3, 5},
}, []string{"path"})
prometheus.Unregister(prometheus.NewGoCollector())
prometheus.Unregister(prometheus.NewGoCollector()) //nolint
if err := prometheus.Register(res.totalRequests); err != nil {
log.Printf("[WARN] can't register prometheus totalRequests, %v", err)

View File

@ -14,21 +14,28 @@ import (
"github.com/umputun/reproxy/app/discovery"
)
func headersHandler(headers []string) func(next http.Handler) http.Handler {
func headersHandler(addHeaders, dropHeaders []string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if len(headers) == 0 {
if len(addHeaders) == 0 && len(dropHeaders) == 0 {
next.ServeHTTP(w, r)
return
}
for _, h := range headers {
// add headers to response
for _, h := range addHeaders {
elems := strings.Split(h, ":")
if len(elems) != 2 {
continue
}
w.Header().Set(strings.TrimSpace(elems[0]), strings.TrimSpace(elems[1]))
}
// drop headers from request
for _, h := range dropHeaders {
r.Header.Del(h)
}
next.ServeHTTP(w, r)
})
}

View File

@ -18,11 +18,17 @@ import (
func Test_headersHandler(t *testing.T) {
wr := httptest.NewRecorder()
handler := headersHandler([]string{"k1:v1", "k2:v2"})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := headersHandler([]string{"k1:v1", "k2:v2"}, []string{"r1", "r2"})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Logf("req: %v", r)
assert.Equal(t, "", r.Header.Get("r1"), "r1 header dropped")
assert.Equal(t, "", r.Header.Get("r2"), "r2 header dropped")
assert.Equal(t, "rv3", r.Header.Get("r3"), "r3 kept")
}))
req, err := http.NewRequest("GET", "http://example.com", nil)
require.NoError(t, err)
req.Header.Set("r1", "rv1")
req.Header.Set("r2", "rv2")
req.Header.Set("r3", "rv3")
handler.ServeHTTP(wr, req)
assert.Equal(t, "v1", wr.Result().Header.Get("k1"))
assert.Equal(t, "v2", wr.Result().Header.Get("k2"))

View File

@ -32,6 +32,7 @@ type Http struct { // nolint golint
MaxBodySize int64
GzEnabled bool
ProxyHeaders []string
DropHeader []string
SSLConfig SSLConfig
Version string
AccessLog io.Writer
@ -45,7 +46,7 @@ type Http struct { // nolint golint
LBSelector func(len int) int
ThrottleSystem int
ThottleUser int
ThrottleUser int
}
// Matcher source info (server and route) to the destination url
@ -110,17 +111,17 @@ func (h *Http) Run(ctx context.Context) error {
}()
handler := R.Wrap(h.proxyHandler(),
R.Recoverer(log.Default()), // recover on errors
signatureHandler(h.Signature, h.Version), // send app signature
h.pingHandler, // respond to /ping
h.healthMiddleware, // respond to /health
h.matchHandler, // set matched routes to context
limiterSystemHandler(h.ThrottleSystem), // limit total requests/sec
limiterUserHandler(h.ThottleUser), // req/seq per user/route match
h.mgmtHandler(), // handles /metrics and /routes for prometheus
h.pluginHandler(), // prc to external plugins
headersHandler(h.ProxyHeaders), // set response headers
accessLogHandler(h.AccessLog), // apache-format log file
R.Recoverer(log.Default()), // recover on errors
signatureHandler(h.Signature, h.Version), // send app signature
h.pingHandler, // respond to /ping
h.healthMiddleware, // respond to /health
h.matchHandler, // set matched routes to context
limiterSystemHandler(h.ThrottleSystem), // limit total requests/sec
limiterUserHandler(h.ThrottleUser), // req/seq per user/route match
h.mgmtHandler(), // handles /metrics and /routes for prometheus
h.pluginHandler(), // prc to external plugins
headersHandler(h.ProxyHeaders, h.DropHeader), // add response headers and delete some request headers
accessLogHandler(h.AccessLog), // apache-format log file
stdoutLogHandler(h.StdOutEnabled, logger.New(logger.Log(log.Default()), logger.Prefix("[INFO]")).Handler),
maxReqSizeHandler(h.MaxBodySize), // limit request max size
gzipHandler(h.GzEnabled), // gzip response