Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(firewall): block peer base on blacklisted CIDR #1309

Merged
merged 11 commits into from
Jun 2, 2024
2 changes: 1 addition & 1 deletion config/example_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@

# `sync.firewall` contains configuration options for the sync firewall.
[sync.firewall]
# `blacklist_addresses` contains the list of addresses that should be blacklisted.
# `blacklist_addresses` contains the list of IPs and CIDRs that should be blacklisted.
# Any connection to blacklisted addresses will be terminated.
blacklist_addresses = []

Expand Down
2 changes: 1 addition & 1 deletion sync/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func DefaultConfig() *Config {

// BasicCheck performs basic checks on the configuration.
func (conf *Config) BasicCheck() error {
return nil
return conf.Firewall.BasicCheck()
}

func (conf *Config) CacheSize() int {
Expand Down
3 changes: 2 additions & 1 deletion sync/firewall/black_list.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"addresses": [
"/ip4/115.193.157.138/tcp/21888"
"115.193.0.0/16",
"240e:390:8a1:ae80:0000:0000:0000:0000/64"
]
}
38 changes: 17 additions & 21 deletions sync/firewall/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ package firewall
import (
_ "embed"
"encoding/json"
"fmt"

"github.com/pactus-project/pactus/util/addr"
"net"
)

//go:embed black_list.json
var _defaultBlackListAddresses []byte
var _defaultBlackListCidrs []byte

type RateLimit struct {
BlockTopic int `toml:"block_topic"`
Expand All @@ -20,18 +18,15 @@ type RateLimit struct {
type Config struct {
BlackListAddresses []string `toml:"blacklist_addresses"`
RateLimit RateLimit `toml:"rate_limit"`

blackListAddrSet map[string]any
}

type defaultBlackListIPs struct {
type defaultBlackListCIDRs struct {
Addresses []string `json:"addresses"`
}

func DefaultConfig() *Config {
return &Config{
BlackListAddresses: make([]string, 0),
blackListAddrSet: make(map[string]any),
RateLimit: RateLimit{
BlockTopic: 0,
TransactionTopic: 3,
Expand All @@ -43,30 +38,31 @@ func DefaultConfig() *Config {
// BasicCheck performs basic checks on the configuration.
func (conf *Config) BasicCheck() error {
for _, address := range conf.BlackListAddresses {
// TODO: use libp2p library (multi-address)
// TODO: address should only contain protocol + address like: "/ip4/1.1.1.1"
_, err := addr.Parse(address)
_, _, err := net.ParseCIDR(address)
if err != nil {
return fmt.Errorf("invalid blacklist address format: %s", address)
return err
}
}

return nil
}

// LoadDefaultBlackListAddresses loads default blacklist addresses from the `black_list.json` file.
func (conf *Config) LoadDefaultBlackListAddresses() {
var def defaultBlackListIPs
func (conf *Config) LoadDefaultBlackListAddresses() error {
var def defaultBlackListCIDRs

_ = json.Unmarshal(_defaultBlackListAddresses, &def)
err := json.Unmarshal(_defaultBlackListCidrs, &def)
if err != nil {
return err
}

for _, a := range def.Addresses {
ma, _ := addr.Parse(a)
conf.blackListAddrSet[ma.Address()] = true
for _, cidr := range def.Addresses {
conf.BlackListAddresses = append(conf.BlackListAddresses, cidr)
}

for _, a := range conf.BlackListAddresses {
ma, _ := addr.Parse(a)
conf.blackListAddrSet[ma.Address()] = true
for _, addr := range conf.BlackListAddresses {
conf.BlackListAddresses = append(conf.BlackListAddresses, addr)
}

return nil
}
51 changes: 41 additions & 10 deletions sync/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"time"

"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/pactus-project/pactus/genesis"
"github.com/pactus-project/pactus/network"
"github.com/pactus-project/pactus/state"
"github.com/pactus-project/pactus/sync/bundle"
"github.com/pactus-project/pactus/sync/peerset"
"github.com/pactus-project/pactus/util/addr"
"github.com/pactus-project/pactus/util/errors"
"github.com/pactus-project/pactus/util/ipblocker"
"github.com/pactus-project/pactus/util/logger"
"github.com/pactus-project/pactus/util/ratelimit"
)
Expand All @@ -26,13 +27,21 @@ type Firewall struct {
transactionRateLimit *ratelimit.RateLimit
consensusRateLimit *ratelimit.RateLimit
state state.Facade
ipBlocker *ipblocker.IPBlocker
logger *logger.SubLogger
}

func NewFirewall(conf *Config, net network.Network, peerSet *peerset.PeerSet, st state.Facade,
log *logger.SubLogger,
) *Firewall {
conf.LoadDefaultBlackListAddresses()
) (*Firewall, error) {
if err := conf.LoadDefaultBlackListAddresses(); err != nil {
return nil, err
}

blocker, err := ipblocker.New(conf.BlackListAddresses)
if err != nil {
return nil, err
}

blockRateLimit := ratelimit.NewRateLimit(conf.RateLimit.BlockTopic, time.Second)
transactionRateLimit := ratelimit.NewRateLimit(conf.RateLimit.TransactionTopic, time.Second)
Expand All @@ -45,9 +54,10 @@ func NewFirewall(conf *Config, net network.Network, peerSet *peerset.PeerSet, st
blockRateLimit: blockRateLimit,
transactionRateLimit: transactionRateLimit,
consensusRateLimit: consensusRateLimit,
ipBlocker: blocker,
state: st,
logger: log,
}
}, nil
}

func (f *Firewall) OpenGossipBundle(data []byte, from peer.ID) *bundle.Bundle {
Expand All @@ -66,16 +76,13 @@ func (f *Firewall) OpenGossipBundle(data []byte, from peer.ID) *bundle.Bundle {
}

func (f *Firewall) IsBlackListAddress(remoteAddr string) bool {
p2pRemoteAddr, err := addr.Parse(remoteAddr)
ip, err := f.getIPFromMultiAddress(remoteAddr)
if err != nil {
f.logger.Debug("firewall: unable to parse remote address", "err", err)

f.logger.Warn("firewall: unable to parse remote address", "err", err, "addr", remoteAddr)
return false
}

_, exists := f.config.blackListAddrSet[p2pRemoteAddr.Address()]

return exists
return f.ipBlocker.IsBlocked(ip)
}

func (f *Firewall) OpenStreamBundle(r io.Reader, from peer.ID) *bundle.Bundle {
Expand Down Expand Up @@ -179,3 +186,27 @@ func (f *Firewall) AllowTransactionRequest() bool {
func (f *Firewall) AllowConsensusRequest() bool {
return f.consensusRateLimit.AllowRequest()
}

func (f *Firewall) getIPFromMultiAddress(address string) (string, error) {
addr, err := multiaddr.NewMultiaddr(address)
if err != nil {
return "", err
}

components := addr.Protocols()

var ip string
for _, comp := range components {
switch comp.Name {
// TODO: can parse dns address and find ip??
case "ip4", "ip6":
ipComponent, err := addr.ValueForProtocol(comp.Code)
if err != nil {
return "", err
}
ip = ipComponent
}
}

return ip, nil
}
65 changes: 56 additions & 9 deletions sync/firewall/firewall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,16 @@ func setup(t *testing.T, conf *Config) *testData {
net := network.MockingNetwork(ts, ts.RandPeerID())

// TODO: It should be like: "/ip6/2a01:4f9:4a:1d85::2"
conf.BlackListAddresses = []string{"/ip6/2a01:4f9:4a:1d85::2/tcp/21888"}
conf.BlackListAddresses = []string{
"84.247.0.0/24",
"115.193.0.0/16",
"240e:390:8a1:ae80:0000:0000:0000:0000/64",
}
require.NoError(t, conf.BasicCheck())
firewall := NewFirewall(conf, net, peerSet, st, subLogger)
firewall, err := NewFirewall(conf, net, peerSet, st, subLogger)
if err != nil {
return nil
}
assert.NotNil(t, firewall)
badPeerID := ts.RandPeerID()
goodPeerID := ts.RandPeerID()
Expand Down Expand Up @@ -206,18 +213,13 @@ func TestBlackListAddress(t *testing.T) {
addr: "/ip4/115.193.157.138/tcp/21888",
blacklisted: true,
},
// TOD: uncomment me
// {
// addr: "/ip4/115.193.157.138",
// blacklisted: true,
// },
{
addr: "/ip4/10.10.10.10",
blacklisted: false,
},
{
addr: "/ip4/10.10.10.10/udp/21888",
blacklisted: false,
addr: "/ip6/240e:390:8a1:ae80:7dbc:64b6:e84c:d2bf/udp/21888",
blacklisted: true,
},
{
addr: "/ip6/2a01:4f9:4a:1d85::2",
Expand Down Expand Up @@ -267,3 +269,48 @@ func TestAllowConsensusRequest(t *testing.T) {
assert.True(t, td.firewall.AllowConsensusRequest())
assert.False(t, td.firewall.AllowConsensusRequest())
}

func TestParseP2PAddr(t *testing.T) {
td := setup(t, nil)

tests := []struct {
name string
address string
expectedIP string
expectError bool
}{
{
name: "Valid IPv4 with p2p",
address: "/ip4/84.247.165.249/tcp/21888/p2p/12D3KooWQmv2FcNQfh1EhA98twt8ePdkQaxEPeYfinEYyVS16juY",
expectedIP: "84.247.165.249",
},
{
name: "Valid IPv4 without p2p",
address: "/ip4/115.193.157.138/tcp/21888",
expectedIP: "115.193.157.138",
},
{
name: "Valid IPv6 with p2p",
address: "/ip6/240e:390:8a1:ae80:7dbc:64b6:e84c:d2bf/tcp/21888/p2p/" +
"12D3KooWQmv2FcNQfh1EhA98twt8ePdkQaxEPeYfinEYyVS16juY",
expectedIP: "240e:390:8a1:ae80:7dbc:64b6:e84c:d2bf",
},
{
name: "Invalid address",
address: "/invalid/address",
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ip, err := td.firewall.getIPFromMultiAddress(tt.address)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedIP, ip)
}
})
}
}
7 changes: 6 additions & 1 deletion sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ func NewSynchronizer(
sync.peerSet = peerset.NewPeerSet(conf.SessionTimeout)
sync.logger = logger.NewSubLogger("_sync", sync)

sync.firewall = firewall.NewFirewall(conf.Firewall, net, sync.peerSet, st, sync.logger)
fw, err := firewall.NewFirewall(conf.Firewall, net, sync.peerSet, st, sync.logger)
if err != nil {
return nil, err
}

sync.firewall = fw

cacheSize := conf.CacheSize()
ca, err := cache.NewCache(conf.CacheSize())
Expand Down
8 changes: 6 additions & 2 deletions sync/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ func TestStop(t *testing.T) {

func TestConnectEvent(t *testing.T) {
conf := testConfig()
conf.Firewall.BlackListAddresses = []string{"/ip4/1.1.1.1/tcp/21888"}
conf.Firewall.BlackListAddresses = []string{
"84.247.0.0/24",
"115.193.0.0/16",
"240e:390:8a1:ae80:7dbc:64b6:e84c:d2bf/64",
}

td := setup(t, conf)

Expand Down Expand Up @@ -260,7 +264,7 @@ func TestConnectEvent(t *testing.T) {
pid = td.RandPeerID()
ce = &network.ConnectEvent{
PeerID: pid,
RemoteAddress: "/ip4/1.1.1.1/tcp/21888",
RemoteAddress: "/ip4/115.193.2.1/tcp/21888",
}
td.network.EventCh <- ce

Expand Down
65 changes: 0 additions & 65 deletions util/addr/addr.go

This file was deleted.

Loading