mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-03-23 21:50:48 +02:00
* Implements -real-client-ip-header option. * The -real-client-ip-header determines what HTTP header is used for determining the "real client IP" of the remote client. * The -real-client-ip-header option supports the following headers: X-Forwarded-For X-ProxyUser-IP and X-Real-IP (default). * Introduces new realClientIPParser interface to allow for multiple polymorphic classes to decide how to determine the real client IP. * TODO: implement the more standard, but more complex `Forwarded` HTTP header. * Corrected order of expected/actual in test cases * Improved error message in getRemoteIP * Add tests for getRemoteIP and getClientString * Add comment explaining splitting of header * Update documentation on -real-client-ip-header w/o -reverse-proxy * Add PR number in changelog. * Fix typo repeated word: "it" Co-Authored-By: Joel Speed <Joel.speed@hotmail.co.uk> * Update extended configuration language * Simplify the language around dependance on -reverse-proxy Co-Authored-By: Joel Speed <Joel.speed@hotmail.co.uk> * Added completions * Reorder real client IP header options * Update CHANGELOG.md * Apply suggestions from code review Co-authored-by: Isabelle COWAN-BERGMAN <Izzette@users.noreply.github.com> Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> Co-authored-by: Henry Jenkins <henry@henryjenkins.name>
103 lines
3.3 KiB
Go
103 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
|
)
|
|
|
|
type realClientIPParser interface {
|
|
GetRealClientIP(http.Header) (net.IP, error)
|
|
}
|
|
|
|
func getRealClientIPParser(headerKey string) (realClientIPParser, error) {
|
|
headerKey = http.CanonicalHeaderKey(headerKey)
|
|
|
|
switch headerKey {
|
|
case http.CanonicalHeaderKey("X-Forwarded-For"), http.CanonicalHeaderKey("X-Real-IP"), http.CanonicalHeaderKey("X-ProxyUser-IP"):
|
|
return &xForwardedForClientIPParser{header: headerKey}, nil
|
|
}
|
|
|
|
// TODO: implement the more standardized but more complex `Forwarded` header.
|
|
return nil, fmt.Errorf("the http header key (%s) is either invalid or unsupported", headerKey)
|
|
}
|
|
|
|
type xForwardedForClientIPParser struct {
|
|
header string
|
|
}
|
|
|
|
// GetRealClientIP obtain the IP address of the end-user (not proxy).
|
|
// Parses headers sharing the format as specified by:
|
|
// * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For.
|
|
// Returns the `<client>` portion specified in the above document.
|
|
// Additionally, is capable of parsing IPs with the port included, for v4 in the format "<ip>:<port>" and for v6 in the
|
|
// format "[<ip>]:<port>". With-port and without-port formats are seamlessly supported concurrently.
|
|
func (p xForwardedForClientIPParser) GetRealClientIP(h http.Header) (net.IP, error) {
|
|
var ipStr string
|
|
if realIP := h.Get(p.header); realIP != "" {
|
|
ipStr = realIP
|
|
} else {
|
|
return nil, nil
|
|
}
|
|
|
|
// Each successive proxy may append itself, comma separated, to the end of the X-Forwarded-for header.
|
|
// Select only the first IP listed, as it is the client IP recorded by the first proxy.
|
|
if commaIndex := strings.IndexRune(ipStr, ','); commaIndex != -1 {
|
|
ipStr = ipStr[:commaIndex]
|
|
}
|
|
ipStr = strings.TrimSpace(ipStr)
|
|
|
|
if ipHost, _, err := net.SplitHostPort(ipStr); err == nil {
|
|
ipStr = ipHost
|
|
}
|
|
|
|
ip := net.ParseIP(ipStr)
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("unable to parse ip (%s) from %s header", ipStr, http.CanonicalHeaderKey(p.header))
|
|
}
|
|
|
|
return ip, nil
|
|
}
|
|
|
|
// getRemoteIP obtains the IP of the low-level connected network host
|
|
func getRemoteIP(req *http.Request) (net.IP, error) {
|
|
if ipStr, _, err := net.SplitHostPort(req.RemoteAddr); err != nil {
|
|
return nil, fmt.Errorf("unable to get ip and port from http.RemoteAddr (%s)", req.RemoteAddr)
|
|
} else if ip := net.ParseIP(ipStr); ip != nil {
|
|
return ip, nil
|
|
} else {
|
|
return nil, fmt.Errorf("unable to parse ip (%s)", ipStr)
|
|
}
|
|
}
|
|
|
|
// getClientString obtains the human readable string of the remote IP and optionally the real client IP if available
|
|
func getClientString(p realClientIPParser, req *http.Request, full bool) (s string) {
|
|
var realClientIPStr string
|
|
if p != nil {
|
|
if realClientIP, err := p.GetRealClientIP(req.Header); err != nil {
|
|
logger.Printf("Unable to get real client IP: %v", err)
|
|
} else if realClientIP != nil {
|
|
realClientIPStr = realClientIP.String()
|
|
}
|
|
}
|
|
|
|
var remoteIPStr string
|
|
if remoteIP, err := getRemoteIP(req); err == nil {
|
|
remoteIPStr = remoteIP.String()
|
|
} else {
|
|
// Should not happen, if it does, likely a bug.
|
|
logger.Printf("Unable to get remote IP(?!?!): %v", err)
|
|
}
|
|
|
|
if !full && realClientIPStr != "" {
|
|
return realClientIPStr
|
|
}
|
|
if full && realClientIPStr != "" {
|
|
return fmt.Sprintf("%s (%s)", remoteIPStr, realClientIPStr)
|
|
}
|
|
return remoteIPStr
|
|
}
|