Skip to content

Commit 314928c

Browse files
committed
empty default; lots of useful lists to use by default in app config
Signed-off-by: hfuss <hayden.fuss@kaleido.io>
1 parent a7b7682 commit 314928c

7 files changed

Lines changed: 164 additions & 126 deletions

File tree

pkg/eventstreams/webhooks_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"testing"
2727

2828
"github.com/hyperledger/firefly-common/pkg/ffapi"
29-
"github.com/hyperledger/firefly-common/pkg/ffnet"
3029
"github.com/hyperledger/firefly-common/pkg/ffresty"
3130
"github.com/hyperledger/firefly-common/pkg/fftls"
3231
"github.com/hyperledger/firefly-common/pkg/wsserver"
@@ -190,9 +189,6 @@ func TestWebhooksTLS(t *testing.T) {
190189
URL: &u,
191190
TLSConfigName: &tlsConfName,
192191
}, func() {
193-
// The test server listens on loopback, which the default SSRF egress denylist blocks;
194-
// disable it for this webhook client so the delivery can reach the local server.
195-
WebhookDefaultsConfig.SubSection("net").Set(ffnet.CIDRDenylist, []string{})
196192
tls0 := TLSConfigs.ArrayEntry(0)
197193
tls0.Set(ConfigTLSConfigName, tlsConfName)
198194
tlsConf := tls0.SubSection("tls")

pkg/ffnet/config.go

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,54 +20,104 @@
2020
package ffnet
2121

2222
import (
23+
"slices"
24+
2325
"github.com/hyperledger/firefly-common/pkg/config"
2426
"github.com/hyperledger/firefly-common/pkg/ffdns"
2527
)
2628

2729
const (
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.
58110
type 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

67118
func InitConfig(conf config.Section) {
68119
ffdns.InitConfig(conf)
69120
conf.AddKnownKey(CIDRDenylist)
70-
conf.AddKnownKey(AdditionalDeniedCIDRs)
71121
}
72122

73123
func 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
}

pkg/ffnet/ffnet.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,12 @@ func NewDialer(ctx context.Context, cfg *Config) (*net.Dialer, error) {
4545
}, nil
4646
}
4747

48-
// effectiveDenylist resolves the base denylist (the configured override, or the secure
49-
// defaults when unset) and appends any additional CIDRs.
50-
func effectiveDenylist(cfg *Config) []string {
51-
base := cfg.CIDRDenylist
52-
if base == nil {
53-
base = DefaultDeniedCIDRs
54-
}
55-
return append(append([]string{}, base...), cfg.AdditionalDeniedCIDRs...)
56-
}
57-
5848
// NewDialControl builds a net.Dialer Control function that rejects connections to any address
5949
// inside the effective CIDR denylist — the core SSRF mitigation. It runs after DNS resolution
6050
// against the actual resolved IP, so it also defeats DNS-rebinding and literal-IP bypasses.
6151
// Returns (nil, nil) when the effective denylist is empty (no restrictions).
6252
func NewDialControl(ctx context.Context, cfg *Config) (func(network, address string, c syscall.RawConn) error, error) {
63-
entries := effectiveDenylist(cfg)
53+
entries := cfg.CIDRDenylist
6454
if len(entries) == 0 {
6555
return nil, nil
6656
}

0 commit comments

Comments
 (0)