2020package ffnet
2121
2222import (
23+ "slices"
24+
2325 "github.com/hyperledger/firefly-common/pkg/config"
2426 "github.com/hyperledger/firefly-common/pkg/ffdns"
2527)
2628
2729const (
28- // CIDRDenylist fully overrides the built-in default denylist of reserved/metadata ranges
29- // blocked to mitigate SSRF. Leave unset to keep the secure defaults. Set to an empty list
30- // to disable denylisting entirely (e.g. to allow a localhost target).
30+ // CIDRDenylist is the list of CIDR ranges to which outbound connections are blocked, as a
31+ // core SSRF mitigation. It is empty by default — ffnet/ffresty is frequently used for private
32+ // service-to-service traffic, so we do not presume which ranges are off-limits. Callers should
33+ // compose an appropriate denylist from the exported building-block lists below (e.g.
34+ // RecommendedSSRFDenylist for externally-reachable/webhook clients, or a narrower set such as
35+ // CloudMetadataCIDRs for internal clients that still want to block IMDS).
3136 CIDRDenylist = "cidrDenylist"
32- // AdditionalDeniedCIDRs appends extra CIDRs on top of the effective base denylist (either
33- // the built-in defaults or a configured cidrDenylist override).
34- AdditionalDeniedCIDRs = "additionalDeniedCIDRs"
3537)
3638
37- // DefaultDeniedCIDRs are blocked by default to mitigate SSRF: loopback, link-local (including
38- // the cloud metadata endpoint 169.254.169.254 and the AWS IMDS IPv6 endpoint), unspecified /
39- // "this host", and multicast / reserved / broadcast ranges. Private RFC1918 / IPv6 ULA ranges
40- // are intentionally NOT included — these dialers are commonly used for legitimate internal
41- // service-to-service calls, so blocking private space is deferred to network firewalls /
42- // zero-trust rather than baked in (callers wanting that can use additionalDeniedCIDRs).
43- var DefaultDeniedCIDRs = []string {
44- "0.0.0.0/8" , // unspecified / "this host" (RFC 1122)
45- "127.0.0.0/8" , // IPv4 loopback
46- "169.254.0.0/16" , // IPv4 link-local, incl. cloud metadata 169.254.169.254
47- "224.0.0.0/4" , // IPv4 multicast
48- "240.0.0.0/4" , // IPv4 reserved (incl. 255.255.255.255 broadcast)
49- "::1/128" , // IPv6 loopback
50- "::/128" , // IPv6 unspecified
51- "fe80::/10" , // IPv6 link-local
52- "fd00:ec2::254/128" , // AWS IMDS IPv6 endpoint (cloud metadata)
53- "ff00::/8" , // IPv6 multicast
54- }
39+ // Exported building-block CIDR lists, grouped by category so callers can concatenate exactly the
40+ // protection they need (see slices.Concat). Each is a distinct, non-overlapping category; the
41+ // metadata endpoints are also called out separately for callers who want only those.
42+ var (
43+ // IPv4Unspecified is the "this host on this network" range 0.0.0.0/8 (RFC 1122). 0.0.0.0
44+ // itself frequently resolves to localhost when dialed.
45+ IPv4Unspecified = []string {"0.0.0.0/8" }
46+ // IPv4Loopback is the IPv4 loopback range 127.0.0.0/8 (RFC 1122).
47+ IPv4Loopback = []string {"127.0.0.0/8" }
48+ // IPv4LinkLocal is the IPv4 link-local range 169.254.0.0/16 (RFC 3927). It contains the
49+ // cloud metadata endpoint 169.254.169.254 (see CloudMetadataCIDRs).
50+ IPv4LinkLocal = []string {"169.254.0.0/16" }
51+ // IPv4Private are the RFC 1918 private ranges.
52+ IPv4Private = []string {
53+ "10.0.0.0/8" ,
54+
55+ "172.16.0.0/12" ,
56+ "192.168.0.0/16" ,
57+ }
58+ // IPv4CGNAT is the carrier-grade NAT / shared address space 100.64.0.0/10 (RFC 6598).
59+ IPv4CGNAT = []string {"100.64.0.0/10" }
60+ // IPv4Multicast is the IPv4 multicast range 224.0.0.0/4 (RFC 5771).
61+ IPv4Multicast = []string {"224.0.0.0/4" }
62+ // IPv4Reserved is the reserved range 240.0.0.0/4 (RFC 1112), which includes the
63+ // 255.255.255.255 limited broadcast address.
64+ IPv4Reserved = []string {"240.0.0.0/4" }
65+
66+ // IPv6Unspecified is the IPv6 unspecified address ::/128 (RFC 4291).
67+ IPv6Unspecified = []string {"::/128" }
68+ // IPv6Loopback is the IPv6 loopback address ::1/128 (RFC 4291).
69+ IPv6Loopback = []string {"::1/128" }
70+ // IPv6LinkLocal is the IPv6 link-local range fe80::/10 (RFC 4291).
71+ IPv6LinkLocal = []string {"fe80::/10" }
72+ // IPv6ULA is the IPv6 unique local address range fc00::/7 (RFC 4193) — the IPv6 equivalent
73+ // of the RFC 1918 private ranges.
74+ IPv6ULA = []string {"fc00::/7" }
75+ // IPv6Multicast is the IPv6 multicast range ff00::/8 (RFC 4291).
76+ IPv6Multicast = []string {"ff00::/8" }
77+
78+ // CloudMetadataCIDRs are the well-known cloud instance metadata endpoints. The common
79+ // 169.254.169.254 endpoint (AWS/GCP/Azure/OpenStack) is already within IPv4LinkLocal; this
80+ // list additionally covers the AWS IMDS IPv6 endpoint, which is a global unicast address and
81+ // so is NOT covered by any of the ranges above. Block this even on internal clients.
82+ CloudMetadataCIDRs = []string {
83+ "169.254.169.254/32" , // AWS/GCP/Azure/OpenStack IMDS (also within IPv4LinkLocal)
84+ "fd00:ec2::254/128" , // AWS IMDS IPv6 endpoint
85+ }
86+
87+ // LoopbackCIDRs blocks loopback and unspecified addresses for both IP families.
88+ LoopbackCIDRs = slices .Concat (IPv4Loopback , IPv4Unspecified , IPv6Loopback , IPv6Unspecified )
89+
90+ // LinkLocalCIDRs blocks link-local addresses (including cloud metadata) for both IP families.
91+ LinkLocalCIDRs = slices .Concat (IPv4LinkLocal , IPv6LinkLocal , CloudMetadataCIDRs )
92+
93+ // PrivateCIDRs blocks the private/internal ranges for both IP families: RFC 1918, CGNAT and
94+ // IPv6 ULA. ffnet does NOT block these by default since service-to-service traffic commonly
95+ // uses them — opt in only for externally-reachable clients.
96+ PrivateCIDRs = slices .Concat (IPv4Private , IPv4CGNAT , IPv6ULA )
97+
98+ // MulticastCIDRs blocks multicast/reserved/broadcast ranges for both IP families.
99+ MulticastCIDRs = slices .Concat (IPv4Multicast , IPv4Reserved , IPv6Multicast )
100+
101+ // SSRFDenylist is the recommended denylist for externally-reachable or
102+ // user-configurable clients (e.g. webhooks): every category above. Internal service-to-service
103+ // clients that must reach private ranges can compose a narrower list instead — e.g.
104+ // slices.Concat(LoopbackCIDRs, LinkLocalCIDRs, MulticastCIDRs), or just CloudMetadataCIDRs.
105+ SSRFDenylist = slices .Concat (LoopbackCIDRs , LinkLocalCIDRs , PrivateCIDRs , MulticastCIDRs )
106+ )
55107
56108// Config is the combined outbound-dialer configuration: the DNS resolver settings plus the
57109// egress CIDR denylist.
58110type Config struct {
59111 DNS ffdns.Config
60- // CIDRDenylist, when non-nil, fully replaces DefaultDeniedCIDRs (an empty non-nil slice
61- // disables denylisting entirely). Leave nil to keep the secure defaults.
112+ // CIDRDenylist is the set of CIDR ranges to block outbound connections to. Empty means no
113+ // restriction. Compose it from the exported building-block lists — e.g.
114+ // SSRFDenylist for any externally-configurable/webhook dialer.
62115 CIDRDenylist []string
63- // AdditionalDeniedCIDRs is appended on top of the effective base denylist.
64- AdditionalDeniedCIDRs []string
65116}
66117
67118func InitConfig (conf config.Section ) {
68119 ffdns .InitConfig (conf )
69120 conf .AddKnownKey (CIDRDenylist )
70- conf .AddKnownKey (AdditionalDeniedCIDRs )
71121}
72122
73123func GenerateConfig (conf config.Section ) (* Config , error ) {
@@ -76,13 +126,10 @@ func GenerateConfig(conf config.Section) (*Config, error) {
76126 return nil , err
77127 }
78128 cfg := & Config {
79- DNS : * dnsCfg ,
80- AdditionalDeniedCIDRs : conf .GetStringSlice (AdditionalDeniedCIDRs ),
81- }
82- // Distinguish "unset" (keep secure defaults) from an explicit override (including an
83- // empty list, which disables denylisting).
84- if conf .IsSet (CIDRDenylist ) {
85- cfg .CIDRDenylist = conf .GetStringSlice (CIDRDenylist )
129+ DNS : * dnsCfg ,
86130 }
131+ // Empty by default (no egress restriction); callers opt in via config or by composing one of
132+ // the exported denylists.
133+ cfg .CIDRDenylist = conf .GetStringSlice (CIDRDenylist )
87134 return cfg , nil
88135}
0 commit comments