Secure network key file and data directory.
This commit is contained in:
parent
d9738b43b3
commit
c5c788a9db
10
Makefile
10
Makefile
|
@ -188,8 +188,8 @@ testnet0 testnet1: | beacon_node signing_process
|
||||||
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
||||||
|
|
||||||
# https://www.gnu.org/software/make/manual/html_node/Multi_002dLine.html
|
# https://www.gnu.org/software/make/manual/html_node/Multi_002dLine.html
|
||||||
define CONNECT_TO_NETWORK
|
define CONNECT_TO_NETWORK =
|
||||||
mkdir -p build/data/shared_$(1)_$(NODE_ID)
|
mkdir -m 0750 -p build/data/shared_$(1)_$(NODE_ID)
|
||||||
|
|
||||||
scripts/make_prometheus_config.sh \
|
scripts/make_prometheus_config.sh \
|
||||||
--nodes 1 \
|
--nodes 1 \
|
||||||
|
@ -204,8 +204,8 @@ define CONNECT_TO_NETWORK
|
||||||
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define CONNECT_TO_NETWORK_IN_DEV_MODE
|
define CONNECT_TO_NETWORK_IN_DEV_MODE =
|
||||||
mkdir -p build/data/shared_$(1)_$(NODE_ID)
|
mkdir -m 0750 -p build/data/shared_$(1)_$(NODE_ID)
|
||||||
|
|
||||||
scripts/make_prometheus_config.sh \
|
scripts/make_prometheus_config.sh \
|
||||||
--nodes 1 \
|
--nodes 1 \
|
||||||
|
@ -221,7 +221,7 @@ endef
|
||||||
|
|
||||||
define CONNECT_TO_NETWORK_WITH_VALIDATOR_CLIENT
|
define CONNECT_TO_NETWORK_WITH_VALIDATOR_CLIENT
|
||||||
# if launching a VC as well - send the BN looking nowhere for validators/secrets
|
# if launching a VC as well - send the BN looking nowhere for validators/secrets
|
||||||
mkdir -p build/data/shared_$(1)_$(NODE_ID)/empty_dummy_folder
|
mkdir -m 0750 -p build/data/shared_$(1)_$(NODE_ID)/empty_dummy_folder
|
||||||
|
|
||||||
scripts/make_prometheus_config.sh \
|
scripts/make_prometheus_config.sh \
|
||||||
--nodes 1 \
|
--nodes 1 \
|
||||||
|
|
|
@ -11,7 +11,7 @@ import
|
||||||
std/[osproc, random],
|
std/[osproc, random],
|
||||||
|
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
stew/[objects, byteutils, endians2], stew/shims/macros,
|
stew/[objects, byteutils, endians2, io2], stew/shims/macros,
|
||||||
chronos, confutils, metrics, json_rpc/[rpcserver, jsonmarshal],
|
chronos, confutils, metrics, json_rpc/[rpcserver, jsonmarshal],
|
||||||
chronicles, bearssl, blscurve,
|
chronicles, bearssl, blscurve,
|
||||||
json_serialization/std/[options, sets, net], serialization/errors,
|
json_serialization/std/[options, sets, net], serialization/errors,
|
||||||
|
@ -243,7 +243,7 @@ proc init*(T: type BeaconNode,
|
||||||
enrForkId = enrForkIdFromState(chainDag.headState.data.data)
|
enrForkId = enrForkIdFromState(chainDag.headState.data.data)
|
||||||
topicBeaconBlocks = getBeaconBlocksTopic(enrForkId.forkDigest)
|
topicBeaconBlocks = getBeaconBlocksTopic(enrForkId.forkDigest)
|
||||||
topicAggregateAndProofs = getAggregateAndProofsTopic(enrForkId.forkDigest)
|
topicAggregateAndProofs = getAggregateAndProofsTopic(enrForkId.forkDigest)
|
||||||
network = createEth2Node(rng, conf, enrForkId)
|
network = createEth2Node(rng, conf, netKeys, enrForkId)
|
||||||
attestationPool = newClone(AttestationPool.init(chainDag, quarantine))
|
attestationPool = newClone(AttestationPool.init(chainDag, quarantine))
|
||||||
exitPool = newClone(ExitPool.init(chainDag, quarantine))
|
exitPool = newClone(ExitPool.init(chainDag, quarantine))
|
||||||
var res = BeaconNode(
|
var res = BeaconNode(
|
||||||
|
@ -894,10 +894,50 @@ proc run*(node: BeaconNode) =
|
||||||
|
|
||||||
var gPidFile: string
|
var gPidFile: string
|
||||||
proc createPidFile(filename: string) =
|
proc createPidFile(filename: string) =
|
||||||
createDir splitFile(filename).dir
|
|
||||||
writeFile filename, $os.getCurrentProcessId()
|
writeFile filename, $os.getCurrentProcessId()
|
||||||
gPidFile = filename
|
gPidFile = filename
|
||||||
addQuitProc proc {.noconv.} = removeFile gPidFile
|
addQuitProc proc {.noconv.} = discard io2.removeFile(gPidFile)
|
||||||
|
|
||||||
|
proc checkDataDir(conf: BeaconNodeConf) =
|
||||||
|
## Checks `conf.dataDir`.
|
||||||
|
## If folder exists, prcoedure will check it for access and
|
||||||
|
## permissions `0750 (rwxr-x---)`, if folder do not exists it will be created
|
||||||
|
## with permissions `0750 (rwxr-x---)`.
|
||||||
|
let dataDir = string(conf.dataDir)
|
||||||
|
when defined(posix):
|
||||||
|
let amask = {AccessFlags.Read, AccessFlags.Write, AccessFlags.Execute}
|
||||||
|
if fileAccessible(dataDir, amask):
|
||||||
|
let gmask = {UserRead, UserWrite, UserExec, GroupRead, GroupExec}
|
||||||
|
let pmask = {OtherRead, OtherWrite, OtherExec, GroupWrite}
|
||||||
|
let pres = getPermissionsSet(dataDir)
|
||||||
|
if pres.isErr():
|
||||||
|
fatal "Could not check data folder permissions",
|
||||||
|
data_dir = dataDir, errorCode = $pres.error,
|
||||||
|
errorMsg = ioErrorMsg(pres.error)
|
||||||
|
quit QuitFailure
|
||||||
|
let insecurePermissions = pres.get() * pmask
|
||||||
|
if insecurePermissions != {}:
|
||||||
|
fatal "Data folder has insecure permissions",
|
||||||
|
data_dir = dataDir,
|
||||||
|
insecure_permissions = $insecurePermissions,
|
||||||
|
current_permissions = pres.get().toString(),
|
||||||
|
required_permissions = gmask.toString()
|
||||||
|
quit QuitFailure
|
||||||
|
else:
|
||||||
|
let res = createPath(dataDir, 0o750)
|
||||||
|
if res.isErr():
|
||||||
|
fatal "Could not create data folder", data_dir = dataDir,
|
||||||
|
errorMsg = ioErrorMsg(res.error), errorCode = $res.error
|
||||||
|
quit QuitFailure
|
||||||
|
elif defined(windows):
|
||||||
|
let res = createPath(dataDir, 0o750)
|
||||||
|
if res.isErr():
|
||||||
|
fatal "Could not create data folder", data_dir = dataDir,
|
||||||
|
errorMsg = ioErrorMsg(res.error), errorCode = $res.error
|
||||||
|
quit QuitFailure
|
||||||
|
else:
|
||||||
|
fatal "Unsupported operation system"
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
proc initializeNetworking(node: BeaconNode) {.async.} =
|
proc initializeNetworking(node: BeaconNode) {.async.} =
|
||||||
await node.network.startListening()
|
await node.network.startListening()
|
||||||
|
@ -1133,6 +1173,8 @@ programMain:
|
||||||
|
|
||||||
case config.cmd
|
case config.cmd
|
||||||
of createTestnet:
|
of createTestnet:
|
||||||
|
checkDataDir(config)
|
||||||
|
|
||||||
let launchPadDeposits = try:
|
let launchPadDeposits = try:
|
||||||
Json.loadFile(config.testnetDepositsFile.string, seq[LaunchPadDeposit])
|
Json.loadFile(config.testnetDepositsFile.string, seq[LaunchPadDeposit])
|
||||||
except SerializationError as err:
|
except SerializationError as err:
|
||||||
|
@ -1193,6 +1235,8 @@ programMain:
|
||||||
cmdParams = commandLineParams(),
|
cmdParams = commandLineParams(),
|
||||||
config
|
config
|
||||||
|
|
||||||
|
checkDataDir(config)
|
||||||
|
|
||||||
createPidFile(config.dataDir.string / "beacon_node.pid")
|
createPidFile(config.dataDir.string / "beacon_node.pid")
|
||||||
|
|
||||||
config.createDumpDirs()
|
config.createDumpDirs()
|
||||||
|
|
|
@ -218,6 +218,12 @@ type
|
||||||
desc: "Write SSZ dumps of blocks, attestations and states to data dir"
|
desc: "Write SSZ dumps of blocks, attestations and states to data dir"
|
||||||
name: "dump" }: bool
|
name: "dump" }: bool
|
||||||
|
|
||||||
|
netKeyFile* {.
|
||||||
|
defaultValue: "random",
|
||||||
|
desc: "Source of network (secp256k1) private key file " &
|
||||||
|
"(random|<path>) (default: random)"
|
||||||
|
name: "netkey-file" }: string
|
||||||
|
|
||||||
of createTestnet:
|
of createTestnet:
|
||||||
testnetDepositsFile* {.
|
testnetDepositsFile* {.
|
||||||
desc: "A LaunchPad deposits file for the genesis state validators"
|
desc: "A LaunchPad deposits file for the genesis state validators"
|
||||||
|
@ -265,6 +271,10 @@ type
|
||||||
desc: "Output file with list of bootstrap nodes for the network"
|
desc: "Output file with list of bootstrap nodes for the network"
|
||||||
name: "output-bootstrap-file" }: OutFile
|
name: "output-bootstrap-file" }: OutFile
|
||||||
|
|
||||||
|
outputNetkeyFile* {.
|
||||||
|
desc: "Output file with network private key for the network"
|
||||||
|
name: "output-netkey-file" }: OutFile
|
||||||
|
|
||||||
of wallets:
|
of wallets:
|
||||||
case walletsCmd* {.command.}: WalletsCmd
|
case walletsCmd* {.command.}: WalletsCmd
|
||||||
of WalletsCmd.create:
|
of WalletsCmd.create:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import
|
||||||
std/options as stdOptions,
|
std/options as stdOptions,
|
||||||
|
|
||||||
# Status libs
|
# Status libs
|
||||||
stew/[varints, base58, base64, endians2, results, byteutils], bearssl,
|
stew/[varints, base58, base64, endians2, results, byteutils, io2], bearssl,
|
||||||
stew/shims/net as stewNet,
|
stew/shims/net as stewNet,
|
||||||
stew/shims/[macros, tables],
|
stew/shims/[macros, tables],
|
||||||
faststreams/[inputs, outputs, buffers], snappy, snappy/framing,
|
faststreams/[inputs, outputs, buffers], snappy, snappy/framing,
|
||||||
|
@ -204,7 +204,6 @@ type
|
||||||
|
|
||||||
const
|
const
|
||||||
clientId* = "Nimbus beacon node v" & fullVersionStr
|
clientId* = "Nimbus beacon node v" & fullVersionStr
|
||||||
networkKeyFilename = "privkey.protobuf"
|
|
||||||
nodeMetadataFilename = "node-metadata.json"
|
nodeMetadataFilename = "node-metadata.json"
|
||||||
|
|
||||||
TCP = net.Protocol.IPPROTO_TCP
|
TCP = net.Protocol.IPPROTO_TCP
|
||||||
|
@ -1201,21 +1200,103 @@ proc initAddress*(T: type MultiAddress, str: string): T =
|
||||||
template tcpEndPoint(address, port): auto =
|
template tcpEndPoint(address, port): auto =
|
||||||
MultiAddress.init(address, tcpProtocol, port)
|
MultiAddress.init(address, tcpProtocol, port)
|
||||||
|
|
||||||
proc getPersistentNetKeys*(
|
proc getPersistentNetKeys*(rng: var BrHmacDrbgContext,
|
||||||
rng: var BrHmacDrbgContext, conf: BeaconNodeConf): KeyPair =
|
conf: BeaconNodeConf): KeyPair =
|
||||||
let
|
case conf.cmd
|
||||||
privKeyPath = conf.dataDir / networkKeyFilename
|
of noCommand:
|
||||||
privKey =
|
if conf.netKeyFile == "random":
|
||||||
if not fileExists(privKeyPath):
|
let res = PrivateKey.random(Secp256k1, rng)
|
||||||
createDir conf.dataDir.string
|
if res.isErr():
|
||||||
let key = PrivateKey.random(Secp256k1, rng).tryGet()
|
fatal "Could not generate random network key file"
|
||||||
writeFile(privKeyPath, key.getBytes().tryGet())
|
quit QuitFailure
|
||||||
key
|
let privKey = res.get()
|
||||||
else:
|
return KeyPair(seckey: privKey, pubkey: privKey.getKey().tryGet())
|
||||||
let keyBytes = readFile(privKeyPath)
|
else:
|
||||||
PrivateKey.init(keyBytes.toOpenArrayByte(0, keyBytes.high)).tryGet()
|
let keyPath =
|
||||||
|
if isAbsolute(conf.netKeyFile):
|
||||||
|
conf.netKeyFile
|
||||||
|
else:
|
||||||
|
conf.dataDir / conf.netKeyFile
|
||||||
|
|
||||||
KeyPair(seckey: privKey, pubkey: privKey.getKey().tryGet())
|
if fileAccessible(keyPath, {AccessFlags.Find}):
|
||||||
|
let gmask = {UserRead, UserWrite}
|
||||||
|
let pmask = {UserExec,
|
||||||
|
GroupRead, GroupWrite, GroupExec,
|
||||||
|
OtherRead, OtherWrite, OtherExec}
|
||||||
|
let pres = getPermissionsSet(keyPath)
|
||||||
|
if pres.isErr():
|
||||||
|
fatal "Could not check key file permissions",
|
||||||
|
key_path = keyPath, errorCode = $pres.error,
|
||||||
|
errorMsg = ioErrorMsg(pres.error)
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let insecurePermissions = pres.get() * pmask
|
||||||
|
if insecurePermissions != {}:
|
||||||
|
fatal "Network key file has insecure permissions",
|
||||||
|
key_path = keyPath,
|
||||||
|
insecure_permissions = $insecurePermissions,
|
||||||
|
current_permissions = pres.get().toString(),
|
||||||
|
required_permissions = gmask.toString()
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let kres = readAllFile(keyPath)
|
||||||
|
if not(kres.isOk()):
|
||||||
|
fatal "Could not read network key file", key_path = keyPath
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let keyBytes = kres.get()
|
||||||
|
|
||||||
|
let rres = PrivateKey.init(keyBytes)
|
||||||
|
if not(rres.isOk()):
|
||||||
|
fatal "Incorrect network key file", key_path = keyPath
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let privKey = rres.get()
|
||||||
|
return KeyPair(seckey: privKey, pubkey: privKey.getKey().tryGet())
|
||||||
|
|
||||||
|
else:
|
||||||
|
let res = PrivateKey.random(Secp256k1, rng)
|
||||||
|
if res.isErr():
|
||||||
|
fatal "Could not generate random network key file"
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let privKey = res.get()
|
||||||
|
|
||||||
|
let wres = writeFile(keyPath, privKey.getBytes().tryGet(), 0o600)
|
||||||
|
if not(wres.isOk()):
|
||||||
|
fatal "Could not write network key file", key_path = keyPath
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
return KeyPair(seckey: privKey, pubkey: privkey.getKey().tryGet())
|
||||||
|
of createTestnet:
|
||||||
|
let netKeyFile = string(conf.outputNetkeyFile)
|
||||||
|
let keyPath =
|
||||||
|
if isAbsolute(netKeyFile):
|
||||||
|
netKeyFile
|
||||||
|
else:
|
||||||
|
conf.dataDir / netKeyFile
|
||||||
|
|
||||||
|
let res = PrivateKey.random(Secp256k1, rng)
|
||||||
|
if res.isErr():
|
||||||
|
fatal "Could not generate random network key file"
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let privKey = res.get()
|
||||||
|
|
||||||
|
let wres = writeFile(keyPath, privKey.getBytes().tryGet(), 0o600)
|
||||||
|
if not(wres.isOk()):
|
||||||
|
fatal "Could not write network key file", key_path = keyPath
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
return KeyPair(seckey: privKey, pubkey: privkey.getKey().tryGet())
|
||||||
|
else:
|
||||||
|
let res = PrivateKey.random(Secp256k1, rng)
|
||||||
|
if res.isErr():
|
||||||
|
fatal "Could not generate random network key file"
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
let privKey = res.get()
|
||||||
|
return KeyPair(seckey: privKey, pubkey: privkey.getKey().tryGet())
|
||||||
|
|
||||||
func gossipId(data: openArray[byte]): string =
|
func gossipId(data: openArray[byte]): string =
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/p2p-interface.md#topics-and-messages
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/p2p-interface.md#topics-and-messages
|
||||||
|
@ -1224,9 +1305,10 @@ func gossipId(data: openArray[byte]): string =
|
||||||
func msgIdProvider(m: messages.Message): string =
|
func msgIdProvider(m: messages.Message): string =
|
||||||
gossipId(m.data)
|
gossipId(m.data)
|
||||||
|
|
||||||
proc createEth2Node*(
|
proc createEth2Node*(rng: ref BrHmacDrbgContext,
|
||||||
rng: ref BrHmacDrbgContext, conf: BeaconNodeConf,
|
conf: BeaconNodeConf,
|
||||||
enrForkId: ENRForkID): Eth2Node =
|
netKeys: KeyPair,
|
||||||
|
enrForkId: ENRForkID): Eth2Node =
|
||||||
var
|
var
|
||||||
(extIp, extTcpPort, extUdpPort) = setupNat(conf)
|
(extIp, extTcpPort, extUdpPort) = setupNat(conf)
|
||||||
hostAddress = tcpEndPoint(conf.listenAddress, conf.tcpPort)
|
hostAddress = tcpEndPoint(conf.listenAddress, conf.tcpPort)
|
||||||
|
@ -1236,11 +1318,10 @@ proc createEth2Node*(
|
||||||
notice "Initializing networking", hostAddress,
|
notice "Initializing networking", hostAddress,
|
||||||
announcedAddresses
|
announcedAddresses
|
||||||
|
|
||||||
let keys = getPersistentNetKeys(rng[], conf)
|
|
||||||
# TODO nim-libp2p still doesn't have support for announcing addresses
|
# TODO nim-libp2p still doesn't have support for announcing addresses
|
||||||
# that are different from the host address (this is relevant when we
|
# that are different from the host address (this is relevant when we
|
||||||
# are running behind a NAT).
|
# are running behind a NAT).
|
||||||
var switch = newStandardSwitch(some keys.seckey, hostAddress,
|
var switch = newStandardSwitch(some netKeys.seckey, hostAddress,
|
||||||
transportFlags = {ServerFlags.ReuseAddr},
|
transportFlags = {ServerFlags.ReuseAddr},
|
||||||
secureManagers = [
|
secureManagers = [
|
||||||
SecureProtocol.Noise, # Only noise in ETH2!
|
SecureProtocol.Noise, # Only noise in ETH2!
|
||||||
|
@ -1257,16 +1338,10 @@ proc createEth2Node*(
|
||||||
|
|
||||||
result = Eth2Node.init(conf, enrForkId, switch, pubsub,
|
result = Eth2Node.init(conf, enrForkId, switch, pubsub,
|
||||||
extIp, extTcpPort, extUdpPort,
|
extIp, extTcpPort, extUdpPort,
|
||||||
keys.seckey.asEthKey, discovery = conf.discv5Enabled,
|
netKeys.seckey.asEthKey,
|
||||||
|
discovery = conf.discv5Enabled,
|
||||||
rng = rng)
|
rng = rng)
|
||||||
|
|
||||||
proc getPersistenBootstrapAddr*(rng: var BrHmacDrbgContext, conf: BeaconNodeConf,
|
|
||||||
ip: ValidIpAddress, port: Port): EnrResult[enr.Record] =
|
|
||||||
let pair = getPersistentNetKeys(rng, conf)
|
|
||||||
return enr.Record.init(1'u64, # sequence number
|
|
||||||
pair.seckey.asEthKey,
|
|
||||||
some(ip), port, port, @[])
|
|
||||||
|
|
||||||
proc announcedENR*(node: Eth2Node): enr.Record =
|
proc announcedENR*(node: Eth2Node): enr.Record =
|
||||||
doAssert node.discovery != nil, "The Eth2Node must be initialized"
|
doAssert node.discovery != nil, "The Eth2Node must be initialized"
|
||||||
node.discovery.localNode.record
|
node.discovery.localNode.record
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 47ff49aae7fdcf9fb7b4af44c6ab2377f04c731e
|
Subproject commit 7a2b6dbddafeeba8c33f5587f08276f58d26deae
|
Loading…
Reference in New Issue