Add allowlist impl

Accepts multiaddrs that define who is allowed.
This commit is contained in:
Marco Munizaga 2022-06-09 22:26:24 -07:00
parent ee3cc3b811
commit dd0cde1cfd
2 changed files with 450 additions and 0 deletions

230
allowlist.go Normal file
View 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
View 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")
}
})
}
}