Set X-Forwarded-* headers
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
parent
d2b38e6ac4
commit
336135c392
3 changed files with 258 additions and 5 deletions
|
@ -1,19 +1,43 @@
|
|||
package forwardedheaders
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/ip"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// XForwarded filter for XForwarded headers.
|
||||
const (
|
||||
xForwardedProto = "X-Forwarded-Proto"
|
||||
xForwardedFor = "X-Forwarded-For"
|
||||
xForwardedHost = "X-Forwarded-Host"
|
||||
xForwardedPort = "X-Forwarded-Port"
|
||||
xForwardedServer = "X-Forwarded-Server"
|
||||
xRealIP = "X-Real-Ip"
|
||||
connection = "Connection"
|
||||
upgrade = "Upgrade"
|
||||
)
|
||||
|
||||
var xHeaders = []string{
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedServer,
|
||||
xRealIP,
|
||||
}
|
||||
|
||||
// XForwarded is an HTTP handler wrapper that sets the X-Forwarded headers, and other relevant headers for a
|
||||
// reverse-proxy. Unless insecure is set, it first removes all the existing values for those headers if the remote
|
||||
// address is not one of the trusted ones.
|
||||
type XForwarded struct {
|
||||
insecure bool
|
||||
trustedIps []string
|
||||
ipChecker *ip.Checker
|
||||
next http.Handler
|
||||
hostname string
|
||||
}
|
||||
|
||||
// NewXForwarded creates a new XForwarded.
|
||||
|
@ -27,11 +51,17 @@ func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XFor
|
|||
}
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "localhost"
|
||||
}
|
||||
|
||||
return &XForwarded{
|
||||
insecure: insecure,
|
||||
trustedIps: trustedIps,
|
||||
ipChecker: ipChecker,
|
||||
next: next,
|
||||
hostname: hostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -42,10 +72,96 @@ func (x *XForwarded) isTrustedIP(ip string) bool {
|
|||
return x.ipChecker.IsAuthorized(ip) == nil
|
||||
}
|
||||
|
||||
// removeIPv6Zone removes the zone if the given IP is an ipv6 address and it has
|
||||
// {zone} information in it, like "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3
|
||||
// Ethernet Adapter - Virtual Switch)]:64692"
|
||||
func removeIPv6Zone(clientIP string) string {
|
||||
return strings.Split(clientIP, "%")[0]
|
||||
}
|
||||
|
||||
// isWebsocketRequest returns whether the specified HTTP request is a
|
||||
// websocket handshake request
|
||||
func isWebsocketRequest(req *http.Request) bool {
|
||||
containsHeader := func(name, value string) bool {
|
||||
items := strings.Split(req.Header.Get(name), ",")
|
||||
for _, item := range items {
|
||||
if value == strings.ToLower(strings.TrimSpace(item)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return containsHeader(connection, "upgrade") && containsHeader(upgrade, "websocket")
|
||||
}
|
||||
|
||||
func forwardedPort(req *http.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if _, port, err := net.SplitHostPort(req.Host); err == nil && port != "" {
|
||||
return port
|
||||
}
|
||||
|
||||
if req.Header.Get(xForwardedProto) == "https" || req.Header.Get(xForwardedProto) == "wss" {
|
||||
return "443"
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
return "443"
|
||||
}
|
||||
|
||||
return "80"
|
||||
}
|
||||
|
||||
func (x *XForwarded) rewrite(outreq *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(outreq.RemoteAddr); err == nil {
|
||||
clientIP = removeIPv6Zone(clientIP)
|
||||
|
||||
if outreq.Header.Get(xRealIP) == "" {
|
||||
outreq.Header.Set(xRealIP, clientIP)
|
||||
}
|
||||
}
|
||||
|
||||
xfProto := outreq.Header.Get(xForwardedProto)
|
||||
if xfProto == "" {
|
||||
if outreq.TLS != nil {
|
||||
outreq.Header.Set(xForwardedProto, "https")
|
||||
} else {
|
||||
outreq.Header.Set(xForwardedProto, "http")
|
||||
}
|
||||
}
|
||||
|
||||
if isWebsocketRequest(outreq) {
|
||||
if outreq.Header.Get(xForwardedProto) == "https" {
|
||||
outreq.Header.Set(xForwardedProto, "wss")
|
||||
} else {
|
||||
outreq.Header.Set(xForwardedProto, "ws")
|
||||
}
|
||||
}
|
||||
|
||||
if xfPort := outreq.Header.Get(xForwardedPort); xfPort == "" {
|
||||
outreq.Header.Set(xForwardedPort, forwardedPort(outreq))
|
||||
}
|
||||
|
||||
if xfHost := outreq.Header.Get(xForwardedHost); xfHost == "" && outreq.Host != "" {
|
||||
outreq.Header.Set(xForwardedHost, outreq.Host)
|
||||
}
|
||||
|
||||
if x.hostname != "" {
|
||||
outreq.Header.Set(xForwardedServer, x.hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !x.insecure && !x.isTrustedIP(r.RemoteAddr) {
|
||||
utils.RemoveHeaders(r.Header, forward.XHeaders...)
|
||||
for _, h := range xHeaders {
|
||||
r.Header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
x.rewrite(r)
|
||||
|
||||
x.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue