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)
|
||||
|
||||
# https://www.gnu.org/software/make/manual/html_node/Multi_002dLine.html
|
||||
define CONNECT_TO_NETWORK
|
||||
mkdir -p build/data/shared_$(1)_$(NODE_ID)
|
||||
define CONNECT_TO_NETWORK =
|
||||
mkdir -m 0750 -p build/data/shared_$(1)_$(NODE_ID)
|
||||
|
||||
scripts/make_prometheus_config.sh \
|
||||
--nodes 1 \
|
||||
|
@ -204,8 +204,8 @@ define CONNECT_TO_NETWORK
|
|||
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
||||
endef
|
||||
|
||||
define CONNECT_TO_NETWORK_IN_DEV_MODE
|
||||
mkdir -p build/data/shared_$(1)_$(NODE_ID)
|
||||
define CONNECT_TO_NETWORK_IN_DEV_MODE =
|
||||
mkdir -m 0750 -p build/data/shared_$(1)_$(NODE_ID)
|
||||
|
||||
scripts/make_prometheus_config.sh \
|
||||
--nodes 1 \
|
||||
|
@ -221,7 +221,7 @@ endef
|
|||
|
||||
define CONNECT_TO_NETWORK_WITH_VALIDATOR_CLIENT
|
||||
# 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 \
|
||||
--nodes 1 \
|
||||
|
|
|
@ -11,7 +11,7 @@ import
|
|||
std/[osproc, random],
|
||||
|
||||
# Nimble packages
|
||||
stew/[objects, byteutils, endians2], stew/shims/macros,
|
||||
stew/[objects, byteutils, endians2, io2], stew/shims/macros,
|
||||
chronos, confutils, metrics, json_rpc/[rpcserver, jsonmarshal],
|
||||
chronicles, bearssl, blscurve,
|
||||
json_serialization/std/[options, sets, net], serialization/errors,
|
||||
|
@ -243,7 +243,7 @@ proc init*(T: type BeaconNode,
|
|||
enrForkId = enrForkIdFromState(chainDag.headState.data.data)
|
||||
topicBeaconBlocks = getBeaconBlocksTopic(enrForkId.forkDigest)
|
||||
topicAggregateAndProofs = getAggregateAndProofsTopic(enrForkId.forkDigest)
|
||||
network = createEth2Node(rng, conf, enrForkId)
|
||||
network = createEth2Node(rng, conf, netKeys, enrForkId)
|
||||
attestationPool = newClone(AttestationPool.init(chainDag, quarantine))
|
||||
exitPool = newClone(ExitPool.init(chainDag, quarantine))
|
||||
var res = BeaconNode(
|
||||
|
@ -894,10 +894,50 @@ proc run*(node: BeaconNode) =
|
|||
|
||||
var gPidFile: string
|
||||
proc createPidFile(filename: string) =
|
||||
createDir splitFile(filename).dir
|
||||
writeFile filename, $os.getCurrentProcessId()
|
||||
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.} =
|
||||
await node.network.startListening()
|
||||
|
@ -1133,6 +1173,8 @@ programMain:
|
|||
|
||||
case config.cmd
|
||||
of createTestnet:
|
||||
checkDataDir(config)
|
||||
|
||||
let launchPadDeposits = try:
|
||||
Json.loadFile(config.testnetDepositsFile.string, seq[LaunchPadDeposit])
|
||||
except SerializationError as err:
|
||||
|
@ -1193,6 +1235,8 @@ programMain:
|
|||
cmdParams = commandLineParams(),
|
||||
config
|
||||
|
||||
checkDataDir(config)
|
||||
|
||||
createPidFile(config.dataDir.string / "beacon_node.pid")
|
||||
|
||||
config.createDumpDirs()
|
||||
|
|
|
@ -218,6 +218,12 @@ type
|
|||
desc: "Write SSZ dumps of blocks, attestations and states to data dir"
|
||||
name: "dump" }: bool
|
||||
|
||||
netKeyFile* {.
|
||||
defaultValue: "random",
|
||||
desc: "Source of network (secp256k1) private key file " &
|
||||
"(random|<path>) (default: random)"
|
||||
name: "netkey-file" }: string
|
||||
|
||||
of createTestnet:
|
||||
testnetDepositsFile* {.
|
||||
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"
|
||||
name: "output-bootstrap-file" }: OutFile
|
||||
|
||||
outputNetkeyFile* {.
|
||||
desc: "Output file with network private key for the network"
|
||||
name: "output-netkey-file" }: OutFile
|
||||
|
||||
of wallets:
|
||||
case walletsCmd* {.command.}: WalletsCmd
|
||||
of WalletsCmd.create:
|
||||
|
|
|
@ -4,7 +4,7 @@ import
|
|||
std/options as stdOptions,
|
||||
|
||||
# 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/[macros, tables],
|
||||
faststreams/[inputs, outputs, buffers], snappy, snappy/framing,
|
||||
|
@ -204,7 +204,6 @@ type
|
|||
|
||||
const
|
||||
clientId* = "Nimbus beacon node v" & fullVersionStr
|
||||
networkKeyFilename = "privkey.protobuf"
|
||||
nodeMetadataFilename = "node-metadata.json"
|
||||
|
||||
TCP = net.Protocol.IPPROTO_TCP
|
||||
|
@ -1201,21 +1200,103 @@ proc initAddress*(T: type MultiAddress, str: string): T =
|
|||
template tcpEndPoint(address, port): auto =
|
||||
MultiAddress.init(address, tcpProtocol, port)
|
||||
|
||||
proc getPersistentNetKeys*(
|
||||
rng: var BrHmacDrbgContext, conf: BeaconNodeConf): KeyPair =
|
||||
let
|
||||
privKeyPath = conf.dataDir / networkKeyFilename
|
||||
privKey =
|
||||
if not fileExists(privKeyPath):
|
||||
createDir conf.dataDir.string
|
||||
let key = PrivateKey.random(Secp256k1, rng).tryGet()
|
||||
writeFile(privKeyPath, key.getBytes().tryGet())
|
||||
key
|
||||
else:
|
||||
let keyBytes = readFile(privKeyPath)
|
||||
PrivateKey.init(keyBytes.toOpenArrayByte(0, keyBytes.high)).tryGet()
|
||||
proc getPersistentNetKeys*(rng: var BrHmacDrbgContext,
|
||||
conf: BeaconNodeConf): KeyPair =
|
||||
case conf.cmd
|
||||
of noCommand:
|
||||
if conf.netKeyFile == "random":
|
||||
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())
|
||||
else:
|
||||
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 =
|
||||
# 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 =
|
||||
gossipId(m.data)
|
||||
|
||||
proc createEth2Node*(
|
||||
rng: ref BrHmacDrbgContext, conf: BeaconNodeConf,
|
||||
enrForkId: ENRForkID): Eth2Node =
|
||||
proc createEth2Node*(rng: ref BrHmacDrbgContext,
|
||||
conf: BeaconNodeConf,
|
||||
netKeys: KeyPair,
|
||||
enrForkId: ENRForkID): Eth2Node =
|
||||
var
|
||||
(extIp, extTcpPort, extUdpPort) = setupNat(conf)
|
||||
hostAddress = tcpEndPoint(conf.listenAddress, conf.tcpPort)
|
||||
|
@ -1236,11 +1318,10 @@ proc createEth2Node*(
|
|||
notice "Initializing networking", hostAddress,
|
||||
announcedAddresses
|
||||
|
||||
let keys = getPersistentNetKeys(rng[], conf)
|
||||
# TODO nim-libp2p still doesn't have support for announcing addresses
|
||||
# that are different from the host address (this is relevant when we
|
||||
# are running behind a NAT).
|
||||
var switch = newStandardSwitch(some keys.seckey, hostAddress,
|
||||
var switch = newStandardSwitch(some netKeys.seckey, hostAddress,
|
||||
transportFlags = {ServerFlags.ReuseAddr},
|
||||
secureManagers = [
|
||||
SecureProtocol.Noise, # Only noise in ETH2!
|
||||
|
@ -1257,16 +1338,10 @@ proc createEth2Node*(
|
|||
|
||||
result = Eth2Node.init(conf, enrForkId, switch, pubsub,
|
||||
extIp, extTcpPort, extUdpPort,
|
||||
keys.seckey.asEthKey, discovery = conf.discv5Enabled,
|
||||
netKeys.seckey.asEthKey,
|
||||
discovery = conf.discv5Enabled,
|
||||
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 =
|
||||
doAssert node.discovery != nil, "The Eth2Node must be initialized"
|
||||
node.discovery.localNode.record
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 47ff49aae7fdcf9fb7b4af44c6ab2377f04c731e
|
||||
Subproject commit 7a2b6dbddafeeba8c33f5587f08276f58d26deae
|
Loading…
Reference in New Issue