Ctrl K
ComunidadGuías…

internal/forwarded/forwarded.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Package forwarded decides who a request came from and over which scheme
// when armdash runs behind a reverse proxy.
//
// The forwarding headers are believed only from peers on a configured list of
// trusted proxies. Anybody else can write them, so from anybody else they are
// ignored and the connection itself is the answer.
package forwarded

import (
	"fmt"
	"net"
	"net/http"
	"net/netip"
	"strings"
)

// Trusted is the list of proxies whose forwarding headers are believed. The
// zero value trusts nobody.
type Trusted struct {
	nets []netip.Prefix
}

// Parse reads a comma-separated list of addresses and CIDR ranges. Host names
// are refused: a lookup per request, or one at startup that later goes stale,
// would both be worse than writing the address.
func Parse(list string) (Trusted, error) {
	var t Trusted
	for _, entry := range strings.Split(list, ",") {
		entry = strings.TrimSpace(entry)
		if entry == "" {
			continue
		}
		var p netip.Prefix
		var err error
		if strings.Contains(entry, "/") {
			p, err = netip.ParsePrefix(entry)
			p = p.Masked()
		} else {
			var a netip.Addr
			if a, err = netip.ParseAddr(entry); err == nil {
				a = a.Unmap()
				p, err = a.Prefix(a.BitLen())
				if a.Zone() != "" {
					err = fmt.Errorf("zone")
				}
			}
		}
		if err != nil {
			return Trusted{}, fmt.Errorf("%q is not an IP address or CIDR range", entry)
		}
		t.nets = append(t.nets, p)
	}
	return t, nil
}

// Empty reports whether nobody is trusted.
func (t Trusted) Empty() bool { return len(t.nets) == 0 }

func (t Trusted) contains(a netip.Addr) bool {
	a = a.Unmap().WithZone("")
	for _, p := range t.nets {
		if p.Contains(a) {
			return true
		}
	}
	return false
}

func peer(r *http.Request) (string, netip.Addr) {
	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		host = r.RemoteAddr
	}
	a, _ := netip.ParseAddr(host)
	return host, a
}

// Proxied reports whether the request came straight from a trusted proxy.
func (t Trusted) Proxied(r *http.Request) bool {
	_, a := peer(r)
	return a.IsValid() && t.contains(a)
}

// Client is the address the request came from. Behind a trusted proxy that is
// the rightmost X-Forwarded-For entry that is not itself a trusted proxy:
// every proxy appends the address it saw, so the entries to its left were
// written by the client and prove nothing.
func (t Trusted) Client(r *http.Request) string {
	host, a := peer(r)
	if !a.IsValid() || !t.contains(a) {
		return host
	}
	var hops []string
	for _, line := range r.Header.Values("X-Forwarded-For") {
		hops = append(hops, strings.Split(line, ",")...)
	}
	for i := len(hops) - 1; i >= 0; i-- {
		hop, ok := parseHop(hops[i])
		if !ok {
			return host
		}
		if !t.contains(hop) {
			return hop.String()
		}
	}
	return host
}

// parseHop takes an X-Forwarded-For entry, which some proxies write with a
// port or in brackets.
func parseHop(s string) (netip.Addr, bool) {
	s = strings.TrimSpace(s)
	if a, err := netip.ParseAddr(s); err == nil {
		return a.Unmap().WithZone(""), true
	}
	if ap, err := netip.ParseAddrPort(s); err == nil {
		return ap.Addr().Unmap().WithZone(""), true
	}
	if a, err := netip.ParseAddr(strings.Trim(s, "[]")); err == nil {
		return a.Unmap().WithZone(""), true
	}
	return netip.Addr{}, false
}

// Scheme is "https" or "http": what a trusted proxy says the browser used, or
// else whether this connection is TLS.
func (t Trusted) Scheme(r *http.Request) string {
	if t.Proxied(r) {
		if vs := r.Header.Values("X-Forwarded-Proto"); len(vs) > 0 {
			last := vs[len(vs)-1]
			if i := strings.LastIndexByte(last, ','); i >= 0 {
				last = last[i+1:]
			}
			if p := strings.TrimSpace(last); p == "http" || p == "https" {
				return p
			}
		}
	}
	if r.TLS != nil {
		return "https"
	}
	return "http"
}