Add reverse proxy configuration support for remote IP address (#14959)
* Add reverse proxy configuration support for remote IP address validation * Trust all IP addresses in containerized environments by default * Use single option to specify networks and proxy IP addresses. By default trust all loopback IPs Co-authored-by: techknowlogick <techknowlogick@gitea.io>tokarchuk/v1.17
parent
6e423d5573
commit
044cd4d016
@ -0,0 +1,42 @@ |
|||||||
|
--- |
||||||
|
kind: pipeline |
||||||
|
name: compliance |
||||||
|
|
||||||
|
platform: |
||||||
|
os: linux |
||||||
|
arch: amd64 |
||||||
|
|
||||||
|
steps: |
||||||
|
- name: lint |
||||||
|
pull: always |
||||||
|
image: golang:1.14 |
||||||
|
commands: |
||||||
|
- make fmt-check |
||||||
|
- make misspell-check |
||||||
|
- make lint |
||||||
|
|
||||||
|
- name: test |
||||||
|
pull: always |
||||||
|
image: golang:1.14 |
||||||
|
commands: |
||||||
|
- make test |
||||||
|
depends_on: |
||||||
|
- lint |
||||||
|
|
||||||
|
- name: coverage |
||||||
|
pull: always |
||||||
|
image: robertstettner/drone-codecov |
||||||
|
settings: |
||||||
|
files: |
||||||
|
- coverage.out |
||||||
|
environment: |
||||||
|
CODECOV_TOKEN: |
||||||
|
from_secret: codecov_token |
||||||
|
depends_on: |
||||||
|
- test |
||||||
|
when: |
||||||
|
branch: |
||||||
|
- master |
||||||
|
event: |
||||||
|
- push |
||||||
|
- pull_request |
@ -0,0 +1,2 @@ |
|||||||
|
vendor/ |
||||||
|
coverage.out |
@ -0,0 +1,9 @@ |
|||||||
|
run: |
||||||
|
timeout: 3m |
||||||
|
|
||||||
|
issues: |
||||||
|
exclude-rules: |
||||||
|
# Exclude some linters from running on tests files. |
||||||
|
- path: _test\.go |
||||||
|
linters: |
||||||
|
- errcheck |
@ -0,0 +1,25 @@ |
|||||||
|
ignoreGeneratedHeader = false |
||||||
|
severity = "warning" |
||||||
|
confidence = 0.8 |
||||||
|
errorCode = 1 |
||||||
|
warningCode = 1 |
||||||
|
|
||||||
|
[rule.blank-imports] |
||||||
|
[rule.context-as-argument] |
||||||
|
[rule.context-keys-type] |
||||||
|
[rule.dot-imports] |
||||||
|
[rule.error-return] |
||||||
|
[rule.error-strings] |
||||||
|
[rule.error-naming] |
||||||
|
[rule.exported] |
||||||
|
[rule.if-return] |
||||||
|
[rule.increment-decrement] |
||||||
|
[rule.var-naming] |
||||||
|
[rule.var-declaration] |
||||||
|
[rule.package-comments] |
||||||
|
[rule.range] |
||||||
|
[rule.receiver-naming] |
||||||
|
[rule.time-naming] |
||||||
|
[rule.unexported-return] |
||||||
|
[rule.indent-error-flow] |
||||||
|
[rule.errorf] |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright (c) 2020 Lauris BH |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
@ -0,0 +1,59 @@ |
|||||||
|
GO ?= go
|
||||||
|
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||||
|
ifeq ($(HAS_GO), GO) |
||||||
|
GOPATH ?= $(shell $(GO) env GOPATH)
|
||||||
|
export PATH := $(GOPATH)/bin:$(PATH)
|
||||||
|
endif |
||||||
|
|
||||||
|
GOFMT ?= gofmt -s
|
||||||
|
|
||||||
|
ifneq ($(RACE_ENABLED),) |
||||||
|
GOTESTFLAGS ?= -race
|
||||||
|
endif |
||||||
|
|
||||||
|
GO_SOURCES := $(wildcard *.go)
|
||||||
|
GO_SOURCES_OWN := $(filter-out vendor/%, $(GO_SOURCES))
|
||||||
|
GO_PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
|
.PHONY: revive |
||||||
|
revive: |
||||||
|
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/mgechev/revive; \
|
||||||
|
fi
|
||||||
|
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
||||||
|
|
||||||
|
.PHONY: golangci-lint |
||||||
|
golangci-lint: |
||||||
|
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
export BINARY="golangci-lint"; \
|
||||||
|
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.26.0; \
|
||||||
|
fi
|
||||||
|
golangci-lint run --timeout 5m
|
||||||
|
|
||||||
|
.PHONY: lint |
||||||
|
lint: golangci-lint revive |
||||||
|
|
||||||
|
.PHONY: fmt |
||||||
|
fmt: |
||||||
|
$(GOFMT) -w $(GO_SOURCES_OWN)
|
||||||
|
|
||||||
|
.PHONY: fmt-check |
||||||
|
fmt-check: |
||||||
|
# get all go files and run go fmt on them
|
||||||
|
@diff=$$($(GOFMT) -d $(GO_SOURCES_OWN)); \
|
||||||
|
if [ -n "$$diff" ]; then \
|
||||||
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
|
echo "$${diff}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi;
|
||||||
|
|
||||||
|
.PHONY: misspell-check |
||||||
|
misspell-check: |
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
misspell -error $(GO_SOURCES_OWN)
|
||||||
|
|
||||||
|
.PHONY: test |
||||||
|
test: |
||||||
|
$(GO) test -cover -coverprofile coverage.out $(GOTESTFLAGS) $(GO_PACKAGES)
|
@ -0,0 +1,46 @@ |
|||||||
|
# [Chi](https://github.com/go-chi/chi) proxy middleware |
||||||
|
|
||||||
|
Forwarded headers middleware to use if application is run behind reverse proxy. |
||||||
|
|
||||||
|
[![Documentation](https://godoc.org/github.com/chi-middleware/proxy?status.svg)](https://pkg.go.dev/github.com/chi-middleware/proxy) |
||||||
|
[![codecov](https://codecov.io/gh/chi-middleware/proxy/branch/master/graph/badge.svg)](https://codecov.io/gh/chi-middleware/proxy) |
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/chi-middleware/proxy)](https://goreportcard.com/report/github.com/chi-middleware/proxy) |
||||||
|
[![Build Status](https://cloud.drone.io/api/badges/chi-middleware/proxy/status.svg?ref=refs/heads/master)](https://cloud.drone.io/chi-middleware/proxy) |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
Import using: |
||||||
|
|
||||||
|
```go |
||||||
|
import "github.com/chi-middleware/proxy" |
||||||
|
``` |
||||||
|
|
||||||
|
Use middleware with default options (trusted from proxy `127.0.0.1` and trusts only last IP address provided in header): |
||||||
|
|
||||||
|
```go |
||||||
|
r := chi.NewRouter() |
||||||
|
r.Use(proxy.ForwardedHeaders()) |
||||||
|
``` |
||||||
|
|
||||||
|
Extend default options: |
||||||
|
|
||||||
|
```go |
||||||
|
r := chi.NewRouter() |
||||||
|
r.Use(proxy.ForwardedHeaders( |
||||||
|
proxy.NewForwardedHeadersOptions(). |
||||||
|
WithForwardLimit(2). |
||||||
|
ClearTrustedProxies().AddTrustedProxy("10.0.0.1"), |
||||||
|
)) |
||||||
|
``` |
||||||
|
|
||||||
|
Provide custom options: |
||||||
|
|
||||||
|
```go |
||||||
|
r := chi.NewRouter() |
||||||
|
r.Use(proxy.ForwardedHeaders(&ForwardedHeadersOptions{ |
||||||
|
ForwardLimit: 1, |
||||||
|
TrustedProxies: []net.IP{ |
||||||
|
net.IPv4(10, 0, 0, 1), |
||||||
|
}, |
||||||
|
})) |
||||||
|
``` |
@ -0,0 +1,8 @@ |
|||||||
|
module github.com/chi-middleware/proxy |
||||||
|
|
||||||
|
go 1.14 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/go-chi/chi/v5 v5.0.1 |
||||||
|
github.com/stretchr/testify v1.7.0 |
||||||
|
) |
@ -0,0 +1,14 @@ |
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||||
|
github.com/go-chi/chi/v5 v5.0.1 h1:ALxjCrTf1aflOlkhMnCUP86MubbWFrzB3gkRPReLpTo= |
||||||
|
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= |
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= |
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= |
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= |
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,77 @@ |
|||||||
|
// Copyright 2020 Lauris BH. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proxy |
||||||
|
|
||||||
|
// Ported from Goji's middleware, source:
|
||||||
|
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") |
||||||
|
var xRealIP = http.CanonicalHeaderKey("X-Real-IP") |
||||||
|
|
||||||
|
// ForwardedHeaders is a middleware that sets a http.Request's RemoteAddr to the results
|
||||||
|
// of parsing either the X-Real-IP header or the X-Forwarded-For header (in that
|
||||||
|
// order).
|
||||||
|
func ForwardedHeaders(options ...*ForwardedHeadersOptions) func(h http.Handler) http.Handler { |
||||||
|
opt := defaultOptions |
||||||
|
if len(options) > 0 { |
||||||
|
opt = options[0] |
||||||
|
} |
||||||
|
return func(h http.Handler) http.Handler { |
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) { |
||||||
|
// Treat unix socket as 127.0.0.1
|
||||||
|
if r.RemoteAddr == "@" { |
||||||
|
r.RemoteAddr = "127.0.0.1:0" |
||||||
|
} |
||||||
|
if rip := realIP(r, opt); len(rip) > 0 { |
||||||
|
r.RemoteAddr = net.JoinHostPort(rip, "0") |
||||||
|
} |
||||||
|
|
||||||
|
h.ServeHTTP(w, r) |
||||||
|
} |
||||||
|
|
||||||
|
return http.HandlerFunc(fn) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func realIP(r *http.Request, options *ForwardedHeadersOptions) string { |
||||||
|
host, _, err := net.SplitHostPort(r.RemoteAddr) |
||||||
|
if err != nil { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
if !options.isTrustedProxy(net.ParseIP(host)) { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
var ip string |
||||||
|
|
||||||
|
if xrip := r.Header.Get(xRealIP); xrip != "" { |
||||||
|
ip = xrip |
||||||
|
} else if xff := r.Header.Get(xForwardedFor); xff != "" { |
||||||
|
p := 0 |
||||||
|
for i := options.ForwardLimit; i > 0; i-- { |
||||||
|
if p > 0 { |
||||||
|
xff = xff[:p-2] |
||||||
|
} |
||||||
|
p = strings.LastIndex(xff, ", ") |
||||||
|
if p < 0 { |
||||||
|
p = 0 |
||||||
|
break |
||||||
|
} else { |
||||||
|
p += 2 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ip = xff[p:] |
||||||
|
} |
||||||
|
|
||||||
|
return ip |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
// Copyright 2020 Lauris BH. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proxy |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
) |
||||||
|
|
||||||
|
// ForwardedHeadersOptions represents options for forwarded header middleware
|
||||||
|
type ForwardedHeadersOptions struct { |
||||||
|
// ForwardLimit limits the number of entries in the headers that will be processed.
|
||||||
|
// The default value is 1. Set to 0 to disable the limit.
|
||||||
|
ForwardLimit int |
||||||
|
// TrustingAllProxies option sets to trust all proxies.
|
||||||
|
TrustingAllProxies bool |
||||||
|
// KnownProxies represents addresses of trusted proxies.
|
||||||
|
TrustedProxies []net.IP |
||||||
|
// TrustedNetworks represents addresses of trusted networks.
|
||||||
|
TrustedNetworks []*net.IPNet |
||||||
|
} |
||||||
|
|
||||||
|
var defaultOptions = &ForwardedHeadersOptions{ |
||||||
|
ForwardLimit: 1, |
||||||
|
TrustedProxies: []net.IP{ |
||||||
|
net.IPv4(127, 0, 0, 1), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// NewForwardedHeadersOptions creates new middleware options
|
||||||
|
func NewForwardedHeadersOptions() *ForwardedHeadersOptions { |
||||||
|
return &ForwardedHeadersOptions{ |
||||||
|
ForwardLimit: defaultOptions.ForwardLimit, |
||||||
|
TrustedProxies: defaultOptions.TrustedProxies, |
||||||
|
TrustedNetworks: defaultOptions.TrustedNetworks, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithForwardLimit sets number of entries to be processed
|
||||||
|
func (opts *ForwardedHeadersOptions) WithForwardLimit(limit int) *ForwardedHeadersOptions { |
||||||
|
opts.ForwardLimit = limit |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
// TrustAllProxies sets to trust all proxies
|
||||||
|
func (opts *ForwardedHeadersOptions) TrustAllProxies() *ForwardedHeadersOptions { |
||||||
|
opts.TrustingAllProxies = true |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
// ClearTrustedProxies clears trusted proxy list
|
||||||
|
func (opts *ForwardedHeadersOptions) ClearTrustedProxies() *ForwardedHeadersOptions { |
||||||
|
opts.TrustingAllProxies = false |
||||||
|
opts.TrustedProxies = make([]net.IP, 0) |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
// AddTrustedProxy adds proxy IP to trusted proxy list
|
||||||
|
func (opts *ForwardedHeadersOptions) AddTrustedProxy(ip string) *ForwardedHeadersOptions { |
||||||
|
// Special option to trust all proxies if IP address is set as wildcard
|
||||||
|
if ip == "*" { |
||||||
|
opts.TrustingAllProxies = true |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
ipaddr := net.ParseIP(ip) |
||||||
|
if ipaddr == nil { |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
opts.TrustedProxies = append(opts.TrustedProxies, ipaddr) |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
// ClearTrustedNetworks clears trusted network list
|
||||||
|
func (opts *ForwardedHeadersOptions) ClearTrustedNetworks() *ForwardedHeadersOptions { |
||||||
|
opts.TrustedNetworks = make([]*net.IPNet, 0) |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
// AddTrustedNetwork adds network to trusted network list
|
||||||
|
func (opts *ForwardedHeadersOptions) AddTrustedNetwork(cidr string) *ForwardedHeadersOptions { |
||||||
|
_, netmask, err := net.ParseCIDR(cidr) |
||||||
|
if err != nil || netmask == nil { |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
opts.TrustedNetworks = append(opts.TrustedNetworks, netmask) |
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
func (opts *ForwardedHeadersOptions) isTrustedProxy(ip net.IP) bool { |
||||||
|
if opts.TrustingAllProxies { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
if ip == nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
for _, tip := range opts.TrustedProxies { |
||||||
|
if tip.Equal(ip) { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for _, tnet := range opts.TrustedNetworks { |
||||||
|
if tnet.Contains(ip) { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
Loading…
Reference in new issue