Resolve real client IP behind trusted proxies (closes #94) #127
+35
-19
@@ -44,6 +44,7 @@ const (
|
|||||||
keyUpstreamConnectionsPerHost = "upstream_connections_per_host"
|
keyUpstreamConnectionsPerHost = "upstream_connections_per_host"
|
||||||
keyCacheMaxBytes = "cache_max_bytes"
|
keyCacheMaxBytes = "cache_max_bytes"
|
||||||
keyBlockedNetworks = "blocked_networks"
|
keyBlockedNetworks = "blocked_networks"
|
||||||
|
keyTrustedProxies = "trusted_proxies"
|
||||||
)
|
)
|
||||||
|
|
||||||
// placeholderSigningKey is the dummy signing_key shipped in
|
// placeholderSigningKey is the dummy signing_key shipped in
|
||||||
@@ -117,6 +118,14 @@ type Config struct {
|
|||||||
// fetcher's dialer; the built-in ranges always apply.
|
// fetcher's dialer; the built-in ranges always apply.
|
||||||
BlockedNetworks []netip.Prefix
|
BlockedNetworks []netip.Prefix
|
||||||
|
|
||||||
|
// TrustedProxies are the CIDR ranges of reverse proxies whose
|
||||||
|
// forwarding headers may be believed. Forwarded headers are honored
|
||||||
|
// only when the immediate peer falls inside one of these ranges;
|
||||||
|
// otherwise the peer address is used and the headers are ignored, so
|
||||||
|
// an untrusted client cannot spoof its address. Empty means trust
|
||||||
|
// nothing and always use the peer address.
|
||||||
|
TrustedProxies []netip.Prefix
|
||||||
|
|
||||||
// CacheMaxBytes is the disk cache size limit in bytes. Zero
|
// CacheMaxBytes is the disk cache size limit in bytes. Zero
|
||||||
// disables the disk cache entirely. When cache_max_bytes is
|
// disables the disk cache entirely. When cache_max_bytes is
|
||||||
// omitted from the configuration, this holds the computed default
|
// omitted from the configuration, this holds the computed default
|
||||||
@@ -185,7 +194,12 @@ func newFromSmartConfig(sc *smartconfig.Config) (*Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockedNetworks, err := getBlockedNetworks(sc)
|
blockedNetworks, err := parseCIDRList(sc, keyBlockedNetworks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trustedProxies, err := parseCIDRList(sc, keyTrustedProxies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -207,6 +221,7 @@ func newFromSmartConfig(sc *smartconfig.Config) (*Config, error) {
|
|||||||
keyUpstreamConnectionsPerHost, DefaultUpstreamConnectionsPerHost),
|
keyUpstreamConnectionsPerHost, DefaultUpstreamConnectionsPerHost),
|
||||||
CacheMaxBytes: loader.int64Val(keyCacheMaxBytes, 0),
|
CacheMaxBytes: loader.int64Val(keyCacheMaxBytes, 0),
|
||||||
BlockedNetworks: blockedNetworks,
|
BlockedNetworks: blockedNetworks,
|
||||||
|
TrustedProxies: trustedProxies,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The computed default for cache_max_bytes needs a validated
|
// The computed default for cache_max_bytes needs a validated
|
||||||
@@ -322,7 +337,8 @@ func isKnownConfigKey(key string) bool {
|
|||||||
switch key {
|
switch key {
|
||||||
case keyDebug, keyMaintenanceMode, keyPort, keyStateDir, keySentryDSN,
|
case keyDebug, keyMaintenanceMode, keyPort, keyStateDir, keySentryDSN,
|
||||||
keyDBURL, keyMetrics, keySigningKey, keyAllowlistHosts, keyAllowHTTP,
|
keyDBURL, keyMetrics, keySigningKey, keyAllowlistHosts, keyAllowHTTP,
|
||||||
keyUpstreamConnectionsPerHost, keyCacheMaxBytes, keyBlockedNetworks, "env":
|
keyUpstreamConnectionsPerHost, keyCacheMaxBytes, keyBlockedNetworks,
|
||||||
|
keyTrustedProxies, "env":
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,27 +833,27 @@ func getStringSlice(sc *smartconfig.Config) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBlockedNetworks parses the blocked_networks value into CIDR prefixes,
|
// parseCIDRList parses the value of the named config key into CIDR
|
||||||
// or returns nil if the key is omitted. It accepts a YAML list of strings
|
// prefixes, or returns nil if the key is omitted. It accepts a YAML list
|
||||||
// or a comma-separated string. An explicitly null value, a wrong type, an
|
// of strings or a comma-separated string. An explicitly null value, a
|
||||||
// empty entry, a non-string entry, or an unparseable CIDR aborts startup
|
// wrong type, an empty entry, a non-string entry, or an unparseable CIDR
|
||||||
// naming the key and the offending value; a default (the built-in
|
// aborts startup naming the key and the offending value; the default
|
||||||
// blocklist alone) applies only to an omitted key.
|
// (an empty list) applies only to an omitted key.
|
||||||
func getBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) {
|
func parseCIDRList(sc *smartconfig.Config, key string) ([]netip.Prefix, error) {
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, ok := sc.Get(keyBlockedNetworks)
|
raw, ok := sc.Get(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if raw == nil {
|
if raw == nil {
|
||||||
return nil, errNullConfigValue(keyBlockedNetworks)
|
return nil, errNullConfigValue(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := blockedNetworkEntries(raw)
|
entries, err := cidrListEntries(raw, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -848,7 +864,7 @@ func getBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) {
|
|||||||
prefix, err := netip.ParsePrefix(entry)
|
prefix, err := netip.ParsePrefix(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("config key %q: value %q is %w",
|
return nil, fmt.Errorf("config key %q: value %q is %w",
|
||||||
keyBlockedNetworks, entry, errNotAValidCIDR)
|
key, entry, errNotAValidCIDR)
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixes = append(prefixes, prefix)
|
prefixes = append(prefixes, prefix)
|
||||||
@@ -857,10 +873,10 @@ func getBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) {
|
|||||||
return prefixes, nil
|
return prefixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// blockedNetworkEntries extracts the raw blocked_networks entries as
|
// cidrListEntries extracts the raw entries of the named CIDR-list key as
|
||||||
// trimmed, non-empty strings, from either a YAML list of strings or a
|
// trimmed, non-empty strings, from either a YAML list of strings or a
|
||||||
// comma-separated string. Any other shape is a configuration error.
|
// comma-separated string. Any other shape is a configuration error.
|
||||||
func blockedNetworkEntries(raw any) ([]string, error) {
|
func cidrListEntries(raw any, key string) ([]string, error) {
|
||||||
switch val := raw.(type) {
|
switch val := raw.(type) {
|
||||||
case []any:
|
case []any:
|
||||||
entries := make([]string, 0, len(val))
|
entries := make([]string, 0, len(val))
|
||||||
@@ -869,12 +885,12 @@ func blockedNetworkEntries(raw any) ([]string, error) {
|
|||||||
str, ok := item.(string)
|
str, ok := item.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("config key %q: list entry %v (%T) is %w",
|
return nil, fmt.Errorf("config key %q: list entry %v (%T) is %w",
|
||||||
keyBlockedNetworks, item, item, errNotAString)
|
key, item, item, errNotAString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(str) == "" {
|
if strings.TrimSpace(str) == "" {
|
||||||
return nil, fmt.Errorf("config key %q: %w",
|
return nil, fmt.Errorf("config key %q: %w",
|
||||||
keyBlockedNetworks, errEmptyListEntry)
|
key, errEmptyListEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, strings.TrimSpace(str))
|
entries = append(entries, strings.TrimSpace(str))
|
||||||
@@ -888,7 +904,7 @@ func blockedNetworkEntries(raw any) ([]string, error) {
|
|||||||
trimmed := strings.TrimSpace(part)
|
trimmed := strings.TrimSpace(part)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
return nil, fmt.Errorf("config key %q: value %q %w",
|
return nil, fmt.Errorf("config key %q: value %q %w",
|
||||||
keyBlockedNetworks, val, errEmptyEntry)
|
key, val, errEmptyEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, trimmed)
|
entries = append(entries, trimmed)
|
||||||
@@ -897,6 +913,6 @@ func blockedNetworkEntries(raw any) ([]string, error) {
|
|||||||
return entries, nil
|
return entries, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("config key %q: value %v (%T) is %w",
|
return nil, fmt.Errorf("config key %q: value %v (%T) is %w",
|
||||||
keyBlockedNetworks, raw, raw, errNotAStringList)
|
key, raw, raw, errNotAStringList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user