Skip to content

Commit

Permalink
Allow getting IP address from "X-Real-Ip", "X-Forwarded-For" headers (#…
Browse files Browse the repository at this point in the history
…45) (joshdvir)

* adding check ip behind lb using headers
* bug fix
* missed ip
* error
* fix location of ip
* tests
* no need
  • Loading branch information
joshdvir authored Feb 27, 2020
1 parent 9a5f524 commit 4c4d2af
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 5 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM debian:stretch as builder
FROM debian:buster as builder
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -q -y golang git-core && \
apt-get clean
Expand All @@ -8,7 +8,7 @@ RUN mkdir -p /root/go/src
COPY rest-api /root/go/src/dyndns
RUN cd /root/go/src/dyndns && go get && go test -v

FROM debian:stretch
FROM debian:buster-slim
MAINTAINER David Prandzioch <[email protected]>

RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
Expand Down
91 changes: 88 additions & 3 deletions rest-api/request_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"bytes"
"errors"
"fmt"
"log"
"net"
Expand Down Expand Up @@ -56,7 +58,13 @@ func BuildWebserviceResponseFromRequest(r *http.Request, appConfig *Config, extr
} else if ipparser.ValidIP6(response.Address) {
response.AddrType = "AAAA"
} else {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
var ip string
var err error

ip, err = getUserIP(r)
if ip == "" {
ip, _, err = net.SplitHostPort(r.RemoteAddr)
}

if err != nil {
response.Success = false
Expand All @@ -68,19 +76,96 @@ func BuildWebserviceResponseFromRequest(r *http.Request, appConfig *Config, extr
// @todo refactor this code to remove duplication
if ipparser.ValidIP4(ip) {
response.AddrType = "A"
response.Address = ip
} else if ipparser.ValidIP6(ip) {
response.AddrType = "AAAA"
response.Address = ip
} else {
response.Success = false
response.Message = fmt.Sprintf("%s is neither a valid IPv4 nor IPv6 address", response.Address)
log.Println(fmt.Sprintf("Invalid address: %s", response.Address))
return response
}

response.Address = ip
}

response.Success = true

return response
}

func getUserIP(r *http.Request) (string, error) {
for _, h := range []string{"X-Real-Ip", "X-Forwarded-For"} {
addresses := strings.Split(r.Header.Get(h), ",")
// march from right to left until we get a public address
// that will be the address right before our proxy.
for i := len(addresses) - 1; i >= 0; i-- {
ip := strings.TrimSpace(addresses[i])
// header can contain spaces too, strip those out.
realIP := net.ParseIP(ip)
if !realIP.IsGlobalUnicast() || isPrivateSubnet(realIP) {
// bad address, go to next
continue
}
return ip, nil
}
}
return "", errors.New("no match")
}

//ipRange - a structure that holds the start and end of a range of ip addresses
type ipRange struct {
start net.IP
end net.IP
}

// inRange - check to see if a given ip address is within a range given
func inRange(r ipRange, ipAddress net.IP) bool {
// strcmp type byte comparison
if bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) < 0 {
return true
}
return false
}

var privateRanges = []ipRange{
ipRange{
start: net.ParseIP("10.0.0.0"),
end: net.ParseIP("10.255.255.255"),
},
ipRange{
start: net.ParseIP("100.64.0.0"),
end: net.ParseIP("100.127.255.255"),
},
ipRange{
start: net.ParseIP("172.16.0.0"),
end: net.ParseIP("172.31.255.255"),
},
ipRange{
start: net.ParseIP("192.0.0.0"),
end: net.ParseIP("192.0.0.255"),
},
ipRange{
start: net.ParseIP("192.168.0.0"),
end: net.ParseIP("192.168.255.255"),
},
ipRange{
start: net.ParseIP("198.18.0.0"),
end: net.ParseIP("198.19.255.255"),
},
}

// isPrivateSubnet - check to see if this ip is in a private subnet
func isPrivateSubnet(ipAddress net.IP) bool {
// my use case is only concerned with ipv4 atm
if ipCheck := ipAddress.To4(); ipCheck != nil {
// iterate over all our ranges
for _, r := range privateRanges {
// check if this ip is in a private range
if inRange(r, ipAddress) {
return true
}
}
}
return false
}

50 changes: 50 additions & 0 deletions rest-api/request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,56 @@ func TestBuildWebserviceResponseFromRequestToReturnValidObject(t *testing.T) {
}
}

func TestBuildWebserviceResponseFromRequestWithXRealIPHeaderToReturnValidObject(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo", nil)
req.Header.Add("X-Real-Ip", "1.2.3.4")
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)

if result.Success != true {
t.Fatalf("Expected WebserviceResponse.Success to be true")
}

if result.Domain != "foo" {
t.Fatalf("Expected WebserviceResponse.Domain to be foo")
}

if result.Address != "1.2.3.4" {
t.Fatalf("Expected WebserviceResponse.Address to be 1.2.3.4")
}

if result.AddrType != "A" {
t.Fatalf("Expected WebserviceResponse.AddrType to be A")
}
}

func TestBuildWebserviceResponseFromRequestWithXForwardedForHeaderToReturnValidObject(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo", nil)
req.Header.Add("X-Forwarded-For", "1.2.3.4")
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)

if result.Success != true {
t.Fatalf("Expected WebserviceResponse.Success to be true")
}

if result.Domain != "foo" {
t.Fatalf("Expected WebserviceResponse.Domain to be foo")
}

if result.Address != "1.2.3.4" {
t.Fatalf("Expected WebserviceResponse.Address to be 1.2.3.4")
}

if result.AddrType != "A" {
t.Fatalf("Expected WebserviceResponse.AddrType to be A")
}
}

func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoSecretIsGiven(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"
Expand Down

0 comments on commit 4c4d2af

Please sign in to comment.