Commit b46237ac authored by Jacob Vosmaer's avatar Jacob Vosmaer

Guess RemoteAddr from X-Forwarded-For

parent 31b10f9e
......@@ -16,6 +16,8 @@ import (
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/upload"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/urlprefix"
"github.com/sebest/xff"
)
var (
......@@ -54,6 +56,9 @@ func (u *Upstream) configureURLPrefix() {
}
func (u *Upstream) ServeHTTP(ow http.ResponseWriter, r *http.Request) {
// Automatic quasi-intelligent X-Forwarded-For parsing
r.RemoteAddr = xff.GetRemoteAddr(r)
w := helper.NewStatsCollectingResponseWriter(ow)
defer w.RequestFinished(r)
......
Copyright (c) 2015 Sebastien Estienne (sebastien.estienne@gmail.com)
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.
# X-Forwarded-For middleware fo Go [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/sebest/xff) [![Build Status](https://travis-ci.org/sebest/xff.svg?branch=master)](https://travis-ci.org/sebest/xff)
Package `xff` is a `net/http` middleware/handler to parse [Forwarded HTTP Extension](http://tools.ietf.org/html/rfc7239) in Golang.
## Example usage
Install `xff`:
go get github.com/sebest/xff
Edit `server.go`:
```go
package main
import (
"net/http"
"github.com/sebest/xff"
)
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello from " + r.RemoteAddr + "\n"))
})
xffmw, _ := xff.Default()
http.ListenAndServe(":8080", xffmw.Handler(handler))
}
```
Then run your server:
go run server.go
The server now runs on `localhost:8080`:
$ curl -D - -H 'X-Forwarded-For: 42.42.42.42' http://localhost:8080/
HTTP/1.1 200 OK
Date: Fri, 20 Feb 2015 20:03:02 GMT
Content-Length: 29
Content-Type: text/plain; charset=utf-8
hello from 42.42.42.42:52661
package xff
import (
"log"
"net"
"net/http"
"os"
"strings"
)
// list of private subnets
var privateMasks, _ = toMasks([]string{
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7",
})
// converts a list of subnets' string to a list of net.IPNet.
func toMasks(ips []string) (masks []net.IPNet, err error) {
for _, cidr := range ips {
var network *net.IPNet
_, network, err = net.ParseCIDR(cidr)
if err != nil {
return
}
masks = append(masks, *network)
}
return
}
// checks if a net.IP is in a list of net.IPNet
func ipInMasks(ip net.IP, masks []net.IPNet) bool {
for _, mask := range masks {
if mask.Contains(ip) {
return true
}
}
return false
}
// IsPublicIP returns true if the given IP can be routed on the Internet.
func IsPublicIP(ip net.IP) bool {
if !ip.IsGlobalUnicast() {
return false
}
return !ipInMasks(ip, privateMasks)
}
// Parse parses the value of the X-Forwarded-For Header and returns the IP address.
func Parse(ipList string) string {
for _, ip := range strings.Split(ipList, ",") {
ip = strings.TrimSpace(ip)
if IP := net.ParseIP(ip); IP != nil && IsPublicIP(IP) {
return ip
}
}
return ""
}
// GetRemoteAddr parses the given request, resolves the X-Forwarded-For header
// and returns the resolved remote address.
func GetRemoteAddr(r *http.Request) string {
return GetRemoteAddrIfAllowed(r, func(sip string) bool { return true })
}
// GetRemoteAddrIfAllowed parses the given request, resolves the X-Forwarded-For header
// and returns the resolved remote address if allowed.
func GetRemoteAddrIfAllowed(r *http.Request, allowed func(sip string) bool) string {
if xffh := r.Header.Get("X-Forwarded-For"); xffh != "" {
if sip, sport, err := net.SplitHostPort(r.RemoteAddr); err == nil && sip != "" {
if allowed(sip) {
if xip := Parse(xffh); xip != "" {
return net.JoinHostPort(xip, sport)
}
}
}
}
return r.RemoteAddr
}
// Options is a configuration container to setup the XFF middleware.
type Options struct {
// AllowedSubnets is a list of Subnets from which we will accept the
// X-Forwarded-For header.
// If this list is empty we will accept every Subnets (default).
AllowedSubnets []string
// Debugging flag adds additional output to debug server side XFF issues.
Debug bool
}
// XFF http handler
type XFF struct {
// Debug logger
Log *log.Logger
// Set to true if all IPs or Subnets are allowed.
allowAll bool
// List of IP subnets that are allowed.
allowedMasks []net.IPNet
}
// New creates a new XFF handler with the provided options.
func New(options Options) (*XFF, error) {
allowedMasks, err := toMasks(options.AllowedSubnets)
if err != nil {
return nil, err
}
xff := &XFF{
allowAll: len(options.AllowedSubnets) == 0,
allowedMasks: allowedMasks,
}
if options.Debug {
xff.Log = log.New(os.Stdout, "[xff] ", log.LstdFlags)
}
return xff, nil
}
// Default creates a new XFF handler with default options.
func Default() (*XFF, error) {
return New(Options{})
}
// Handler updates RemoteAdd from X-Fowarded-For Headers.
func (xff *XFF) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
h.ServeHTTP(w, r)
})
}
// Negroni compatible interface
func (xff *XFF) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
next(w, r)
}
// HandlerFunc provides Martini compatible handler
func (xff *XFF) HandlerFunc(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
}
// checks that the IP is allowed.
func (xff *XFF) allowed(sip string) bool {
if xff.allowAll {
return true
} else if ip := net.ParseIP(sip); ip != nil && ipInMasks(ip, xff.allowedMasks) {
return true
}
return false
}
// convenience method. checks if debugging is turned on before printing
func (xff *XFF) logf(format string, a ...interface{}) {
if xff.Log != nil {
xff.Log.Printf(format, a...)
}
}
......@@ -176,6 +176,12 @@
"revision": "46f70867da7b79c74c21ef022c4a47f138af3d27",
"revisionTime": "2017-01-16T09:20:13Z"
},
{
"checksumSHA1": "bQ+Wb430AXpen54AYtrR1Igfh18=",
"path": "github.com/sebest/xff",
"revision": "6c115e0ffa35d6a2e3f7a9e797c9cf07f0da4b9f",
"revisionTime": "2016-09-10T04:38:05Z"
},
{
"checksumSHA1": "ySaT8G3I3y4MmnoXOYAAX0rC+p8=",
"path": "github.com/sirupsen/logrus",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment