mirror of
https://github.com/libp2p/go-libp2p-resource-manager.git
synced 2025-03-10 17:20:39 +08:00
Add allowlist impl
Accepts multiaddrs that define who is allowed.
This commit is contained in:
parent
ee3cc3b811
commit
dd0cde1cfd
230
allowlist.go
Normal file
230
allowlist.go
Normal file
@ -0,0 +1,230 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
)
|
||||
|
||||
type allowlist struct {
|
||||
// TODO do we want to make this lookup faster?
|
||||
|
||||
// Any peer with these IPs are allowed
|
||||
allowedIPs []net.IP
|
||||
allowedNetworks []*net.IPNet
|
||||
|
||||
// Only the specified peers can use these IPs
|
||||
allowedPeerByIP map[peer.ID][]net.IP
|
||||
allowedPeerByNetwork map[peer.ID][]*net.IPNet
|
||||
}
|
||||
|
||||
func newAllowList() allowlist {
|
||||
return allowlist{
|
||||
allowedPeerByIP: make(map[peer.ID][]net.IP),
|
||||
allowedPeerByNetwork: make(map[peer.ID][]*net.IPNet),
|
||||
}
|
||||
}
|
||||
|
||||
func toIPOrIPNet(ma multiaddr.Multiaddr) (net.IP, *net.IPNet, string, error) {
|
||||
var ipString string
|
||||
var mask string
|
||||
var allowedPeer string
|
||||
|
||||
multiaddr.ForEach(ma, func(c multiaddr.Component) bool {
|
||||
if c.Protocol().Code == multiaddr.P_IP4 || c.Protocol().Code == multiaddr.P_IP6 {
|
||||
ipString = c.Value()
|
||||
}
|
||||
if c.Protocol().Code == multiaddr.P_IPCIDR {
|
||||
mask = c.Value()
|
||||
}
|
||||
if c.Protocol().Code == multiaddr.P_P2P {
|
||||
allowedPeer = c.Value()
|
||||
}
|
||||
return ipString == "" || mask == "" || allowedPeer == ""
|
||||
})
|
||||
|
||||
if ipString == "" {
|
||||
return nil, nil, allowedPeer, errors.New("missing ip address")
|
||||
}
|
||||
|
||||
if mask == "" {
|
||||
ip := net.ParseIP(ipString)
|
||||
if ip == nil {
|
||||
return nil, nil, allowedPeer, errors.New("invalid ip address")
|
||||
}
|
||||
return ip, nil, allowedPeer, nil
|
||||
}
|
||||
|
||||
_, ipnet, err := net.ParseCIDR(ipString + "/" + mask)
|
||||
return nil, ipnet, allowedPeer, err
|
||||
|
||||
}
|
||||
|
||||
func (al *allowlist) Add(ma multiaddr.Multiaddr) error {
|
||||
ip, ipnet, allowedPeerStr, err := toIPOrIPNet(ma)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if allowedPeerStr != "" {
|
||||
// We have a peerID constraint
|
||||
allowedPeer, err := peer.Decode(allowedPeerStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ip != nil {
|
||||
al.allowedPeerByIP[allowedPeer] = append(al.allowedPeerByIP[allowedPeer], ip)
|
||||
} else if ipnet != nil {
|
||||
al.allowedPeerByNetwork[allowedPeer] = append(al.allowedPeerByNetwork[allowedPeer], ipnet)
|
||||
}
|
||||
} else {
|
||||
if ip != nil {
|
||||
al.allowedIPs = append(al.allowedIPs, ip)
|
||||
} else if ipnet != nil {
|
||||
al.allowedNetworks = append(al.allowedNetworks, ipnet)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al *allowlist) Remove(ma multiaddr.Multiaddr) error {
|
||||
ip, ipnet, allowedPeerStr, err := toIPOrIPNet(ma)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipList := al.allowedIPs
|
||||
ipNetList := al.allowedNetworks
|
||||
|
||||
var allowedPeer peer.ID
|
||||
if allowedPeerStr != "" {
|
||||
// We have a peerID constraint
|
||||
allowedPeer, err = peer.Decode(allowedPeerStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipList = al.allowedPeerByIP[allowedPeer]
|
||||
ipNetList = al.allowedPeerByNetwork[allowedPeer]
|
||||
}
|
||||
|
||||
if ip != nil {
|
||||
i := len(ipList)
|
||||
for i > 0 {
|
||||
i--
|
||||
if ipList[i].Equal(ip) {
|
||||
if i == len(ipList)-1 {
|
||||
// Trim this element from the end
|
||||
ipList = ipList[:i]
|
||||
} else {
|
||||
// swap remove
|
||||
ipList[i] = ipList[len(ipList)-1]
|
||||
ipList = ipList[:len(ipList)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ipnet != nil {
|
||||
i := len(ipNetList)
|
||||
for i > 0 {
|
||||
i--
|
||||
if ipNetList[i].IP.Equal(ipnet.IP) && bytes.Equal(ipNetList[i].Mask, ipnet.Mask) {
|
||||
if i == len(ipNetList)-1 {
|
||||
// Trim this element from the end
|
||||
ipNetList = ipNetList[:i]
|
||||
} else {
|
||||
// swap remove
|
||||
ipNetList[i] = ipNetList[len(ipNetList)-1]
|
||||
ipNetList = ipNetList[:len(ipNetList)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allowedPeer != "" {
|
||||
al.allowedPeerByIP[allowedPeer] = ipList
|
||||
al.allowedPeerByNetwork[allowedPeer] = ipNetList
|
||||
} else {
|
||||
al.allowedIPs = ipList
|
||||
al.allowedNetworks = ipNetList
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al *allowlist) Allowed(ma multiaddr.Multiaddr) bool {
|
||||
ip, err := manet.ToIP(ma)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, allowedIP := range al.allowedIPs {
|
||||
if allowedIP.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range al.allowedNetworks {
|
||||
if network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, allowedIPs := range al.allowedPeerByIP {
|
||||
for _, allowedIP := range allowedIPs {
|
||||
if allowedIP.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, allowedNetworks := range al.allowedPeerByNetwork {
|
||||
for _, network := range allowedNetworks {
|
||||
if network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (al *allowlist) AllowedPeerAndMultiaddr(peerID peer.ID, ma multiaddr.Multiaddr) bool {
|
||||
ip, err := manet.ToIP(ma)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, allowedIP := range al.allowedIPs {
|
||||
if allowedIP.Equal(ip) {
|
||||
// We found a match that isn't constrained by a peerID
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range al.allowedNetworks {
|
||||
if network.Contains(ip) {
|
||||
// We found a match that isn't constrained by a peerID
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if expectedIPs, ok := al.allowedPeerByIP[peerID]; ok {
|
||||
for _, expectedIP := range expectedIPs {
|
||||
if expectedIP.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expectedNetworks, ok := al.allowedPeerByNetwork[peerID]; ok {
|
||||
for _, expectedNetwork := range expectedNetworks {
|
||||
if expectedNetwork.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
220
allowlist_test.go
Normal file
220
allowlist_test.go
Normal file
@ -0,0 +1,220 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/test"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
func TestAllowed(t *testing.T) {
|
||||
allowlist := newAllowList()
|
||||
ma, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.4/tcp/1234")
|
||||
err := allowlist.Add(ma)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add ip4: %s", err)
|
||||
}
|
||||
|
||||
if !allowlist.Allowed(ma) {
|
||||
t.Fatalf("addr should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedNetwork(t *testing.T) {
|
||||
allowlist := newAllowList()
|
||||
ma, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.0/ipcidr/24")
|
||||
err := allowlist.Add(ma)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add ip4: %s", err)
|
||||
}
|
||||
|
||||
ma2, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.20/tcp/1234")
|
||||
if !allowlist.Allowed(ma2) {
|
||||
t.Fatalf("addr should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedPeerOnIP(t *testing.T) {
|
||||
allowlist := newAllowList()
|
||||
p, err := test.RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to gen peer ip4: %s", err)
|
||||
}
|
||||
|
||||
ma, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.4/p2p/" + peer.Encode(p))
|
||||
err = allowlist.Add(ma)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add ip4: %s", err)
|
||||
}
|
||||
|
||||
ma2, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.4")
|
||||
if !allowlist.AllowedPeerAndMultiaddr(p, ma2) {
|
||||
t.Fatalf("addr should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedPeerOnNetwork(t *testing.T) {
|
||||
allowlist := newAllowList()
|
||||
p, err := test.RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to gen peer ip4: %s", err)
|
||||
}
|
||||
|
||||
ma, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.0/ipcidr/24/p2p/" + peer.Encode(p))
|
||||
err = allowlist.Add(ma)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add ip4: %s", err)
|
||||
}
|
||||
|
||||
ma2, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.4")
|
||||
if !allowlist.AllowedPeerAndMultiaddr(p, ma2) {
|
||||
t.Fatalf("addr should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedWithPeer(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
allowlist []string
|
||||
endpoint multiaddr.Multiaddr
|
||||
peer peer.ID
|
||||
isAllowed bool
|
||||
}
|
||||
|
||||
peerA := test.RandPeerIDFatal(t)
|
||||
peerB := test.RandPeerIDFatal(t)
|
||||
multiaddrA, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.4/tcp/1234")
|
||||
multiaddrB, _ := multiaddr.NewMultiaddr("/ip4/2.2.3.4/tcp/1234")
|
||||
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "Blocked",
|
||||
isAllowed: false,
|
||||
allowlist: []string{"/ip4/1.2.3.1"},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "Blocked wrong peer",
|
||||
isAllowed: false,
|
||||
allowlist: []string{"/ip4/1.2.3.4" + "/p2p/" + peer.Encode(peerB)},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "allowed on network",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/1.2.3.0/ipcidr/24"},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "Blocked peer not on network",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/1.2.3.0/ipcidr/24"},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
}, {
|
||||
name: "allowed. right network, right peer",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/1.2.3.0/ipcidr/24" + "/p2p/" + peer.Encode(peerA)},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
}, {
|
||||
name: "allowed. right network, no peer",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/1.2.3.0/ipcidr/24"},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "Blocked. right network, wrong peer",
|
||||
isAllowed: false,
|
||||
allowlist: []string{"/ip4/1.2.3.0/ipcidr/24" + "/p2p/" + peer.Encode(peerB)},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "allowed peer any ip",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/0.0.0.0/ipcidr/0"},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "allowed peer multiple ips in allowlist",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/1.2.3.4/p2p/" + peer.Encode(peerA), "/ip4/2.2.3.4/p2p/" + peer.Encode(peerA)},
|
||||
endpoint: multiaddrA,
|
||||
peer: peerA,
|
||||
},
|
||||
{
|
||||
name: "allowed peer multiple ips in allowlist",
|
||||
isAllowed: true,
|
||||
allowlist: []string{"/ip4/1.2.3.4/p2p/" + peer.Encode(peerA), "/ip4/2.2.3.4/p2p/" + peer.Encode(peerA)},
|
||||
endpoint: multiaddrB,
|
||||
peer: peerA,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
allowlist := newAllowList()
|
||||
for _, maStr := range tc.allowlist {
|
||||
ma, err := multiaddr.NewMultiaddr(maStr)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to parse multiaddr: %s", err)
|
||||
}
|
||||
allowlist.Add(ma)
|
||||
}
|
||||
|
||||
if allowlist.AllowedPeerAndMultiaddr(tc.peer, tc.endpoint) != tc.isAllowed {
|
||||
t.Fatalf("%v: expected %v", !tc.isAllowed, tc.isAllowed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoved(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
allowedMA string
|
||||
}
|
||||
peerA := test.RandPeerIDFatal(t)
|
||||
maA, _ := multiaddr.NewMultiaddr("/ip4/1.2.3.4")
|
||||
|
||||
testCases := []testCase{
|
||||
{name: "ip4", allowedMA: "/ip4/1.2.3.4"},
|
||||
{name: "ip4 with peer", allowedMA: "/ip4/1.2.3.4/p2p/" + peer.Encode(peerA)},
|
||||
{name: "ip4 network", allowedMA: "/ip4/0.0.0.0/ipcidr/0"},
|
||||
{name: "ip4 network with peer", allowedMA: "/ip4/0.0.0.0/ipcidr/0/p2p/" + peer.Encode(peerA)},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
allowlist := newAllowList()
|
||||
ma, err := multiaddr.NewMultiaddr(tc.allowedMA)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse ma: %s", err)
|
||||
}
|
||||
|
||||
err = allowlist.Add(ma)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add ip4: %s", err)
|
||||
}
|
||||
|
||||
if !allowlist.AllowedPeerAndMultiaddr(peerA, maA) {
|
||||
t.Fatalf("addr should be allowed")
|
||||
}
|
||||
|
||||
allowlist.Remove((ma))
|
||||
|
||||
if allowlist.AllowedPeerAndMultiaddr(peerA, maA) {
|
||||
t.Fatalf("addr should not be allowed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user