Secure network key file and data directory.

This commit is contained in:
cheatfate 2020-08-19 16:12:10 +03:00 committed by zah
parent d9738b43b3
commit c5c788a9db
5 changed files with 168 additions and 39 deletions

View File

@ -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 \

View File

@ -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()

View File

@ -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:

View File

@ -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

2
vendor/nim-stew vendored

@ -1 +1 @@
Subproject commit 47ff49aae7fdcf9fb7b4af44c6ab2377f04c731e Subproject commit 7a2b6dbddafeeba8c33f5587f08276f58d26deae