Merge pull request #15 from jpmorganchase/permissions-rebased

implemented node permissioning
This commit is contained in:
Patrick Mylund Nielsen 2016-11-18 10:17:44 -05:00 committed by GitHub
commit a961389cc6
10 changed files with 202 additions and 22 deletions

View File

@ -156,6 +156,7 @@ participating.
utils.VoteMinBlockTimeFlag,
utils.VoteMaxBlockTimeFlag,
utils.SingleBlockMakerFlag,
utils.EnableNodePermissionFlag,
}
app.Flags = append(app.Flags, debug.Flags...)

View File

@ -369,6 +369,10 @@ var (
Name: "singleblockmaker",
Usage: "Indicate this node is the only node that can create blocks",
}
EnableNodePermissionFlag = cli.BoolFlag{
Name: "permissioned",
Usage: "If enabled, the node will allow only a defined list of nodes to connect",
}
)
// MakeDataDir retrieves the currently requested data directory, terminating
@ -588,28 +592,29 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
}
config := &node.Config{
DataDir: MakeDataDir(ctx),
KeyStoreDir: ctx.GlobalString(KeyStoreDirFlag.Name),
UseLightweightKDF: ctx.GlobalBool(LightKDFFlag.Name),
PrivateKey: MakeNodeKey(ctx),
Name: name,
Version: vsn,
UserIdent: makeNodeUserIdent(ctx),
NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name),
BootstrapNodes: MakeBootstrapNodes(ctx),
ListenAddr: MakeListenAddress(ctx),
NAT: MakeNAT(ctx),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
IPCPath: MakeIPCPath(ctx),
HTTPHost: MakeHTTPRpcHost(ctx),
HTTPPort: ctx.GlobalInt(RPCPortFlag.Name),
HTTPCors: ctx.GlobalString(RPCCORSDomainFlag.Name),
HTTPModules: MakeRPCModules(ctx.GlobalString(RPCApiFlag.Name)),
WSHost: MakeWSRpcHost(ctx),
WSPort: ctx.GlobalInt(WSPortFlag.Name),
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
DataDir: MakeDataDir(ctx),
KeyStoreDir: ctx.GlobalString(KeyStoreDirFlag.Name),
UseLightweightKDF: ctx.GlobalBool(LightKDFFlag.Name),
PrivateKey: MakeNodeKey(ctx),
Name: name,
Version: vsn,
UserIdent: makeNodeUserIdent(ctx),
NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name),
BootstrapNodes: MakeBootstrapNodes(ctx),
ListenAddr: MakeListenAddress(ctx),
NAT: MakeNAT(ctx),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
IPCPath: MakeIPCPath(ctx),
HTTPHost: MakeHTTPRpcHost(ctx),
HTTPPort: ctx.GlobalInt(RPCPortFlag.Name),
HTTPCors: ctx.GlobalString(RPCCORSDomainFlag.Name),
HTTPModules: MakeRPCModules(ctx.GlobalString(RPCApiFlag.Name)),
WSHost: MakeWSRpcHost(ctx),
WSPort: ctx.GlobalInt(WSPortFlag.Name),
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
EnableNodePermission: ctx.GlobalBool(EnableNodePermissionFlag.Name),
}
if ctx.GlobalBool(DevModeFlag.Name) {
if !ctx.GlobalIsSet(DataDirFlag.Name) {

View File

@ -12,6 +12,7 @@ QUORUM OPTIONS:
--singleblockmaker Indicate this node is the only node that can create blocks
--minblocktime value Set minimum block time (default: 3)
--maxblocktime value Set max block time (default: 10)
--permissioned If enabled, the node will allow only a defined list of nodes to connect
```
The full list of arguments can be viewed by running `geth --help`.
@ -144,3 +145,29 @@ All scripts can be found in the `7nodes` folder in the `quorum-examples` reposit
1. Step 1, run `init.sh` and initialize data directories (change variables accordingly)
2. Step 2, start nodes with `start.sh` (change variables accordingly)
3. Step 3, stop network with `stop.sh`
## Permissioned Network
Node Permissioning is a feature that controls which nodes can connect to a given node and also to which nodes this node can dial out to. Currently, it is managed at individual node level by the command line flag `--permissioned` while starting the node.
If the `--permissioned` node is present, the node looks for a file named `<data-dir>/permissioned-nodes.json`. This file contains the list of enodes that this node can connect to and also accepts connections only from those nodes. In other words, if permissioning is enabled, only the nodes that are listed in this file become part of the network. It is an error to enable `--permissioned` but not have the `permissioned-nodes.json` file. If the flag is given, but no nodes are present in this file, then this node can neither connect to any node or accept any incoming connections.
The `permissioned-nodes.json` follows following pattern (similar to `static-nodes.json`):
```json
[
"enode://enodehash1@ip1:port1",
"enode://enodehash2@ip2:port2",
"enode://enodehash3@ip3:port3",
]
```
Sample file:
```json
[
"enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300",
]
```
In the current release, every node has its own copy of `permissioned-nodes.json`. In a future release, the permissioned nodes list will be moved to a smart contract, thereby keeping the list on chain and one global list of nodes that connect to the network.

View File

@ -67,6 +67,7 @@ type Config struct {
NetworkId int // Network ID to use for selecting peers to connect to
Genesis string // Genesis JSON to seed the chain database with
SingleBlockMaker bool // Assume this node is the only node on the network allowed to create blocks
EnableNodePermission bool //Used for enabling / disabling node permissioning
SkipBcVersionCheck bool // e.g. blockchain export
DatabaseCache int

View File

@ -158,6 +158,9 @@ type Config struct {
// If the module list is empty, all RPC API endpoints designated public will be
// exposed.
WSModules []string
//enables node level Permissioning
EnableNodePermission bool
}
// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into

View File

@ -79,6 +79,8 @@ type Node struct {
stop chan struct{} // Channel to wait for termination notifications
lock sync.RWMutex
}
// New creates a new P2P node, ready for protocol registration.
@ -167,6 +169,9 @@ func (n *Node) Start() error {
NoDial: n.config.NoDial,
MaxPeers: n.config.MaxPeers,
MaxPendingPeers: n.config.MaxPendingPeers,
EnableNodePermission: n.config.EnableNodePermission,
DataDir: n.config.DataDir,
}
running := &p2p.Server{Config: n.serverConfig}
glog.V(logger.Info).Infoln("instance:", n.serverConfig.Name)

80
p2p/permissions.go Normal file
View File

@ -0,0 +1,80 @@
package p2p
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p/discover"
)
const (
NODE_NAME_LENGTH = 32
PERMISSIONED_CONFIG = "permissioned-nodes.json"
)
// check if a given node is permissioned to connect to the change
func isNodePermissioned(nodename string, currentNode string, datadir string, direction string) bool {
var permissonedList []string
nodes := parsePermissionedNodes(datadir)
for _, v := range nodes {
permissonedList = append(permissonedList, v.ID.String())
}
glog.V(logger.Debug).Infof("Permisssioned_list %v", permissonedList)
for _, v := range permissonedList {
if v == nodename {
glog.V(logger.Debug).Infof("isNodePermissioned <%v> connection:: nodename <%v> ALLOWED-BY <%v>", direction, nodename[:NODE_NAME_LENGTH], currentNode[:NODE_NAME_LENGTH])
return true
}
glog.V(logger.Debug).Infof("isNodePermissioned <%v> connection:: nodename <%v> DENIED-BY <%v>", direction, nodename[:NODE_NAME_LENGTH], currentNode[:NODE_NAME_LENGTH])
}
glog.V(logger.Debug).Infof("isNodePermissioned <%v> connection:: nodename <%v> DENIED-BY <%v>", direction, nodename[:NODE_NAME_LENGTH], currentNode[:NODE_NAME_LENGTH])
return false
}
//this is a shameless copy from the config.go. It is a duplication of the code
//for the timebeing to allow reload of the permissioned nodes while the server is running
func parsePermissionedNodes(DataDir string) []*discover.Node {
glog.V(logger.Debug).Infof("parsePermissionedNodes DataDir %v, file %v", DataDir, PERMISSIONED_CONFIG)
path := filepath.Join(DataDir, PERMISSIONED_CONFIG)
if _, err := os.Stat(path); err != nil {
glog.V(logger.Error).Infof("Read Error for permissioned-nodes.json file %v. This is because 'permissioned' flag is specified but no permissioned-nodes.json file is present.", err)
return nil
}
// Load the nodes from the config file
blob, err := ioutil.ReadFile(path)
if err != nil {
glog.V(logger.Error).Infof("parsePermissionedNodes: Failed to access nodes: %v", err)
return nil
}
nodelist := []string{}
if err := json.Unmarshal(blob, &nodelist); err != nil {
glog.V(logger.Error).Infof("parsePermissionedNodes: Failed to load nodes: %v", err)
return nil
}
// Interpret the list as a discovery node array
var nodes []*discover.Node
for _, url := range nodelist {
if url == "" {
glog.V(logger.Error).Infof("parsePermissionedNodes: Node URL blank")
continue
}
node, err := discover.ParseNode(url)
if err != nil {
glog.V(logger.Error).Infof("parsePermissionedNodes: Node URL %s: %v\n", url, err)
continue
}
nodes = append(nodes, node)
}
return nodes
}

View File

@ -116,6 +116,12 @@ type Config struct {
// If NoDial is true, the server will not dial any peers.
NoDial bool
//Enables Permissioning
EnableNodePermission bool
//DataDir
DataDir string
}
// Server manages all peer connections.
@ -638,6 +644,30 @@ func (srv *Server) setupConn(fd net.Conn, flags connFlag, dialDest *discover.Nod
c.close(err)
return
}
//START - QUORUM Permissioning
currentNode := srv.NodeInfo().ID
cnodeName := srv.NodeInfo().Name
glog.V(logger.Debug).Infof("EnableNodePermission <%v>, DataDir <%v>, Current Node ID <%v>, Node Name <%v>, Dialed Dest<%v>, Connection ID <%v>, Connection String <%v> ", srv.EnableNodePermission, srv.DataDir, currentNode, cnodeName, dialDest, c.id, c.id.String())
if srv.EnableNodePermission {
glog.V(logger.Debug).Infof("Node Permissioning is Enabled. ")
node := c.id.String()
direction := "INCOMING"
if dialDest != nil {
node = dialDest.ID.String()
direction = "OUTGOING"
glog.V(logger.Debug).Infof("Connection Direction <%v>", direction)
}
if !isNodePermissioned(node, currentNode, srv.DataDir, direction) {
return
}
} else {
glog.V(logger.Debug).Infof("Node Permissioning is Disabled. ")
}
//END - QUORUM Permissioning
// For dialed connections, check that the remote public key matches.
if dialDest != nil && c.id != dialDest.ID {
c.close(DiscUnexpectedIdentity)

View File

@ -0,0 +1,15 @@
[
"enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300",
"enode://e4d8738cd275024de9799669f60e2d5b6b4adc9439430748906e1cec5a4c0aed736000b24bf6c75bfe4c2ac768bdbe365046a98b624ba4641b451847a7150c1b@192.168.0.105:30302",
"enode://920b930bcd285cda9edf4c0c92a65026cabfb18d820d121178dacef8902b93203b87b8f5acd01715ff3864cbff041d69affd739e2521d81ac1e40c6b17d25ee@192.168.0.105:30303",
"enode://a311f122aa3b2d9deffc721f39fa9ee4816dcec68e7845b5a1c6875ecaf4fcadec48c0b6a6e50bda78e44bd68cf966a3c92509c4abe09fe32b282cae9d1d1d1b@192.168.0.105:30304",
"enode://58a71648e004675ae30f6e0953cc89cbbd05a5f6744d2fd15cc04b8923d4079185acefb669d608081b00aa5bfd03722f4843b4c71d9021ff8cb3a25b95673773@192.168.0.105:30305",
"enode://aaae04bb274dabf04f03a18548bd763c999ac88d029a756ad69e8082178d6dc10d3ed5be50947ee57478f4c5ae8c73dc88774b2992c942477c589e50a826b0d6@192.168.0.105:30306",
"enode://9287b30ed6753eb27a017daa4f23504979070d3353de2a5dc572f46ab338393ee8c5afe00b79d28e8bffab0774c4ecdcfb62e5ac5a51c4544e350fae309fae21@192.168.0.105:30307",
"enode://ce70feafc1ca25d815b492b2625625c2a79c7fe551a75ea42a9b28e5c956b117eab0fb86e3f344b51ef8fcdef0d7c3c4305d89089d1b8fbbafd52607679f4475@192.168.0.105:30308",
"enode://66f5ad6594aadb6cc7111b033304c3e03a5bf274d2535a3c15613125d35f3a489426569ae587f2d55deffcfda93766fe264fc139f6601ef1932d7113278f27e3@192.168.0.105:30309"
]

13
permissioned-nodes.json Normal file
View File

@ -0,0 +1,13 @@
[
"enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300",
"enode://e4d8738cd275024de9799669f60e2d5b6b4adc9439430748906e1cec5a4c0aed736000b24bf6c75bfe4c2ac768bdbe365046a98b624ba4641b451847a7150c1b@192.168.0.105:30302",
"enode://920b930bcd285cda9edf4c0c92a65026cabfb18d820d121178dacef8902b93203b87b8f5acd01715ff3864cbff041d69affd739e2521d81ac1e40c6b17d25ee@192.168.0.105:30303",
"enode://a311f122aa3b2d9deffc721f39fa9ee4816dcec68e7845b5a1c6875ecaf4fcadec48c0b6a6e50bda78e44bd68cf966a3c92509c4abe09fe32b282cae9d1d1d1b@192.168.0.105:30304",
"enode://58a71648e004675ae30f6e0953cc89cbbd05a5f6744d2fd15cc04b8923d4079185acefb669d608081b00aa5bfd03722f4843b4c71d9021ff8cb3a25b95673773@192.168.0.105:30305",
"enode://aaae04bb274dabf04f03a18548bd763c999ac88d029a756ad69e8082178d6dc10d3ed5be50947ee57478f4c5ae8c73dc88774b2992c942477c589e50a826b0d6@192.168.0.105:30306",
"enode://9287b30ed6753eb27a017daa4f23504979070d3353de2a5dc572f46ab338393ee8c5afe00b79d28e8bffab0774c4ecdcfb62e5ac5a51c4544e350fae309fae21@192.168.0.105:30307",
"enode://ce70feafc1ca25d815b492b2625625c2a79c7fe551a75ea42a9b28e5c956b117eab0fb86e3f344b51ef8fcdef0d7c3c4305d89089d1b8fbbafd52607679f4475@192.168.0.105:30308",
"enode://66f5ad6594aadb6cc7111b033304c3e03a5bf274d2535a3c15613125d35f3a489426569ae587f2d55deffcfda93766fe264fc139f6601ef1932d7113278f27e3@192.168.0.105:30309"
]