Skip to content
This repository has been archived by the owner on Aug 19, 2022. It is now read-only.

feat: Allowlist #47

Merged
merged 24 commits into from
Jul 1, 2022
Merged

feat: Allowlist #47

merged 24 commits into from
Jul 1, 2022

Conversation

MarcoPolo
Copy link
Contributor

@MarcoPolo MarcoPolo commented Jun 10, 2022

This adds the Allowlist to Resource manager. See docs/allowlist.md for the design doc and more details.

This change requires changes to be merged first in go-libp2p-core and go-multiaddr (to support the ipcidr protocol in the multiaddr).

After this is merged, go-libp2p will need to be plumbed with these changes as well.

Before Merge:

After Merge:

  • Another PR for the config input to define an allowlist. The allowlist supports dynamic updating, but it's not exposed yet.
  • Update go-libp2p to pass in the endpoint information

@MarcoPolo MarcoPolo marked this pull request as ready for review June 13, 2022 23:39
@MarcoPolo
Copy link
Contributor Author

@marten-seemann / @vyzo this is ready to review. Please take a look.

Before merging I'll make sure to finish this checkboxed things in the PR description.

@marten-seemann marten-seemann self-requested a review June 14, 2022 18:00
Copy link
Contributor

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is my understanding correct, that we allow two types of addresses to be whitelisted?

  1. /ip{4,6}/<ip>/
  2. /ip{4,6}/<ip>/p2p/<peerid>
    But not /p2p/<peerid>?

We also need to add configurable limits for the newly introduced allowlist scopes, right?

allowlist.go Outdated Show resolved Hide resolved
allowlist.go Outdated Show resolved Hide resolved
allowlist.go Outdated Show resolved Hide resolved
allowlist.go Outdated Show resolved Hide resolved
allowedNetworks []*net.IPNet

// Only the specified peers can use these IPs
allowedPeerByNetwork map[peer.ID][]*net.IPNet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered unifying these into a single map? map[net.IPNet]struct{ allowsAll bool; map[peer.ID]struct{} }
allowsAll would be set if a multiaddr without /p2p is added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I use net.IPNet as a key? The struct holds two byteslices, so I don't think I can.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked, you can't. That's annoying. You could use net.IPNet.String(), but not sure if that's nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think so because:

  1. allow checking is harder. you have to convert between the string and bytes.
  2. You need to allocate the string on insertion.
  3. you need to allocate at least once when doing an allowlist check.
  4. (Depending on check implementation) you lose the quick lookup by peerid. Which may be a pretty common use case.

peer *peerScope
dir network.Direction
usefd bool
isAllowlisted bool
Copy link
Contributor

@marten-seemann marten-seemann Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: isAllowListed. Or newAllowlist instead of newAllowList.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to call this allowlist or Allowlist. Purposely not capitalizing the l. I realize I should change newAllowList to newAllowlist to be consistent.

rcmgr_test.go Outdated Show resolved Hide resolved
allowlist.go Outdated Show resolved Hide resolved
@marten-seemann marten-seemann requested a review from vyzo June 14, 2022 18:22
@marten-seemann
Copy link
Contributor

Requesting a review from @vyzo. Can you check how the scopes are getting shifted around?

@MarcoPolo
Copy link
Contributor Author

We also need to add configurable limits for the newly introduced allowlist scopes, right?

Yep! Thank you for reminding me. I just added those with the same limits as the standard scopes.

Is my understanding correct, that we allow two types of addresses to be whitelisted?

1. `/ip{4,6}/<ip>/`

2. `/ip{4,6}/<ip>/p2p/<peerid>`
   But not `/p2p/<peerid>`?

Correct. I'm not sure what value the /p2p/<peerid> would give you since we'd have no way of knowing a connection was for this peerid until after we get the peerid information. Maybe if we knew that this address was signed by the <peerid> then we could allowlist the connection attempt. But I think you can do that with this by Adding the addresses you discover are signed by the peer you trust to the allowlist with the peer's peer id appended.

@marten-seemann
Copy link
Contributor

Correct. I'm not sure what value the /p2p/ would give you since we'd have no way of knowing a connection was for this peerid until after we get the peerid information.

I agree. I was misled by the ipNet != nil check into assuming that we can have addresses that don't have an IP address.

@MarcoPolo
Copy link
Contributor Author

Friendly ping @vyzo :)

@marten-seemann
Copy link
Contributor

@MarcoPolo Do you want to include the config changes in this PR, or are you planning to create a follow-up PR for that?

@MarcoPolo
Copy link
Contributor Author

@MarcoPolo Do you want to include the config changes in this PR, or are you planning to create a follow-up PR for that?

My plan was after this (see pr description). Since this will work fine without it and the config change is pretty small. And you have some changes around config in #48 in progress.

Copy link
Collaborator

@vyzo vyzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a public interface and accessor in the rcmgr for the allowlist, plus an allowlist option to initialize.

allowlist.go Outdated
manet "github.com/multiformats/go-multiaddr/net"
)

type allowlist struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have an interface type assertion for this, right after the def?
Or is there no public interface?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to make a public interface for it in extapi, and provide an accessor so that users can modify after initialization.
The RWMutex is a must in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow the reason for a public interface? I thought generally interfaces belonged to the caller.

Interfaces declare the behaviour the caller requires not the behaviour the type will provide. Let callers define an interface that describes the behaviour they expect. The interface belongs to them, the consumer, not you.

From: https://dave.cheney.net/practical-go/presentations/gophercon-israel.html#_let_callers_define_the_interface_they_require

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah is it because we return an interface and we can't have an interface function return this unexported type?

What's the downside of exporting this type then?

allowlist.go Outdated Show resolved Hide resolved
rcmgr.go Outdated Show resolved Hide resolved
@vyzo
Copy link
Collaborator

vyzo commented Jun 27, 2022

Another important consideration here: we should only check the allow list if and only if a regular connection reservation fails.
This way it only triggers at the limit and we avoid the computational cost in the happy path.

@MarcoPolo
Copy link
Contributor Author

We need a public interface and accessor in the rcmgr for the allowlist, plus an allowlist option to initialize.

I exported the Allowlist type, added an accessor to rcmgr, and added the WithAllowlistedMultiaddrs option.

@BigLep BigLep linked an issue Jun 28, 2022 that may be closed by this pull request
@BigLep BigLep mentioned this pull request Jun 28, 2022
41 tasks
var allowedPeer peer.ID
var isIPV4 bool

multiaddr.ForEach(ma, func(c multiaddr.Component) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use ma.MultiaddrToIPNet here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, if we want to extract the p2p component at the same time, right? We could get that one without iterating again by splitting off the last component though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also need the isIPV4 to save some time below

allowlist.go Outdated Show resolved Hide resolved
allowlist.go Show resolved Hide resolved
allowlist.go Show resolved Hide resolved
}

i := len(ipNetList)
for i > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use for i, ipnet := range ipNetList? Do we gain anything from going through the list in reverse order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For swap remove. If we go through in normal order then we would skip an entry after swap remove or do some other messy logic that I think boils down to reverse iterating

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That only applies if you're trying to make multiple deletions from the list. Should be fine if you're breaking after the first deletion, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah that's true. Although I'm a bit hesitant to change this so that instead of working for N removals it only works for 1 removal and would subtly break if you try to remove more than 1.

And the only reason to make the change is so that we use the syntactic sugar of for ... range. Is there any other benefit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'd consider range more idiomatic (makes it harder to produce out-of-bounds access). No need to change it here.

allowlist.go Outdated Show resolved Hide resolved
allowlist.go Show resolved Hide resolved
allowlist_test.go Outdated Show resolved Hide resolved
@MarcoPolo
Copy link
Contributor Author

@marten-seemann anything left?

@MarcoPolo MarcoPolo merged commit 6607ffe into master Jul 1, 2022
@MarcoPolo MarcoPolo deleted the marco/allowlist branch July 1, 2022 18:59
@ajnavarro ajnavarro mentioned this pull request Aug 24, 2022
72 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Defend against eclipse attacks with ALLOW-list support
3 participants