295 lines
10 KiB
Nim
295 lines
10 KiB
Nim
|
{.used.}
|
||
|
|
||
|
import
|
||
|
std/[typetraits, os, options, json, sequtils, uri, algorithm],
|
||
|
testutils/unittests, chronicles, stint, json_serialization, confutils,
|
||
|
chronos, eth/keys, blscurve, libp2p/crypto/crypto as lcrypto,
|
||
|
stew/[byteutils, io2], stew/shims/net, nimcrypto/utils,
|
||
|
|
||
|
../beacon_chain/spec/[crypto, keystore, eth2_merkleization],
|
||
|
../beacon_chain/spec/datatypes/base,
|
||
|
../beacon_chain/spec/eth2_apis/[rest_beacon_client, rest_keymanager_types],
|
||
|
../beacon_chain/validators/[keystore_management, slashing_protection_common],
|
||
|
../beacon_chain/networking/network_metadata,
|
||
|
../beacon_chain/rpc/rest_key_management_api,
|
||
|
../beacon_chain/[conf, filepath, beacon_node,
|
||
|
nimbus_beacon_node, beacon_node_status],
|
||
|
|
||
|
./testutil
|
||
|
|
||
|
const
|
||
|
simulationDepositsCount = 128
|
||
|
dataDir = "./test_keymanager_api"
|
||
|
validatorsDir = dataDir / "validators"
|
||
|
secretsDir = dataDir / "secrets"
|
||
|
depositsFile = dataDir / "deposits.json"
|
||
|
genesisFile = dataDir / "genesis.ssz"
|
||
|
bootstrapEnrFile = dataDir / "bootstrap_node.enr"
|
||
|
tokenFilePath = dataDir / "keymanager-token.txt"
|
||
|
keymanagerPort = 47000
|
||
|
correctTokenValue = "some secret token"
|
||
|
|
||
|
proc startSingleNodeNetwork =
|
||
|
let
|
||
|
rng = keys.newRng()
|
||
|
mnemonic = generateMnemonic(rng[])
|
||
|
seed = getSeed(mnemonic, KeyStorePass.init "")
|
||
|
cfg = defaultRuntimeConfig
|
||
|
|
||
|
let vres = secureCreatePath(validatorsDir)
|
||
|
if vres.isErr():
|
||
|
warn "Could not create validators folder",
|
||
|
path = validatorsDir, err = ioErrorMsg(vres.error)
|
||
|
|
||
|
let sres = secureCreatePath(secretsDir)
|
||
|
if sres.isErr():
|
||
|
warn "Could not create secrets folder",
|
||
|
path = secretsDir, err = ioErrorMsg(sres.error)
|
||
|
|
||
|
let deposits = generateDeposits(
|
||
|
cfg,
|
||
|
rng[],
|
||
|
seed,
|
||
|
0, simulationDepositsCount,
|
||
|
validatorsDir,
|
||
|
secretsDir,
|
||
|
KeystoreMode.Fast)
|
||
|
|
||
|
if deposits.isErr:
|
||
|
fatal "Failed to generate deposits", err = deposits.error
|
||
|
quit 1
|
||
|
|
||
|
let launchPadDeposits =
|
||
|
mapIt(deposits.value, LaunchPadDeposit.init(cfg, it))
|
||
|
|
||
|
Json.saveFile(depositsFile, launchPadDeposits)
|
||
|
notice "Deposit data written", filename = depositsFile
|
||
|
|
||
|
let tokenFileRes = secureWriteFile(tokenFilePath, correctTokenValue)
|
||
|
if tokenFileRes.isErr:
|
||
|
fatal "Failed to create token file", err = deposits.error
|
||
|
quit 1
|
||
|
|
||
|
let createTestnetConf = BeaconNodeConf.load(cmdLine = mapIt([
|
||
|
"--data-dir=" & dataDir,
|
||
|
"createTestnet",
|
||
|
"--total-validators=" & $simulationDepositsCount,
|
||
|
"--deposits-file=" & depositsFile,
|
||
|
"--output-genesis=" & genesisFile,
|
||
|
"--output-bootstrap-file=" & bootstrapEnrFile,
|
||
|
"--netkey-file=network_key.json",
|
||
|
"--insecure-netkey-password=true",
|
||
|
"--genesis-offset=0"], TaintedString it))
|
||
|
|
||
|
doCreateTestnet(createTestnetConf, rng[])
|
||
|
|
||
|
let runNodeConf = BeaconNodeConf.load(cmdLine = mapIt([
|
||
|
"--tcp-port=49000",
|
||
|
"--udp-port=49000",
|
||
|
"--network=" & dataDir,
|
||
|
"--data-dir=" & dataDir,
|
||
|
"--validators-dir=" & validatorsDir,
|
||
|
"--secrets-dir=" & secretsDir,
|
||
|
"--metrics-address=127.0.0.1",
|
||
|
"--metrics-port=48008",
|
||
|
"--rest-address=127.0.0.1",
|
||
|
"--rest-port=" & $keymanagerPort,
|
||
|
"--keymanager=true",
|
||
|
"--keymanager-address=127.0.0.1",
|
||
|
"--keymanager-port=" & $keymanagerPort,
|
||
|
"--keymanager-token-file=" & tokenFilePath,
|
||
|
"--doppelganger-detection=off"], TaintedString it))
|
||
|
|
||
|
let metadata = loadEth2NetworkMetadata(dataDir)
|
||
|
|
||
|
let node = BeaconNode.init(
|
||
|
metadata.cfg,
|
||
|
rng,
|
||
|
runNodeConf,
|
||
|
metadata.depositContractDeployedAt,
|
||
|
metadata.eth1Network,
|
||
|
metadata.genesisData,
|
||
|
metadata.genesisDepositsSnapshot
|
||
|
)
|
||
|
|
||
|
node.start() # This will run until the node is terminated by
|
||
|
# setting its `bnStatus` to `Stopping`.
|
||
|
|
||
|
os.removeDir dataDir
|
||
|
|
||
|
const
|
||
|
password = "7465737470617373776f7264f09f9491"
|
||
|
# This is taken from the offical test vectors in test_keystores.nim
|
||
|
secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||
|
salt = hexToSeqByte "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
|
||
|
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
|
||
|
secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736"
|
||
|
|
||
|
proc runTests {.async.} =
|
||
|
while bnStatus != BeaconNodeStatus.Running:
|
||
|
await sleepAsync(1.seconds)
|
||
|
|
||
|
await sleepAsync(2.seconds)
|
||
|
|
||
|
let
|
||
|
client = RestClientRef.new(initTAddress("127.0.0.1", keymanagerPort))
|
||
|
rng = keys.newRng()
|
||
|
privateKey = ValidatorPrivKey.fromRaw(secretBytes).get
|
||
|
|
||
|
newKeystore = createKeystore(
|
||
|
kdfPbkdf2, rng[], privateKey,
|
||
|
KeystorePass.init password,
|
||
|
salt = salt, iv = iv,
|
||
|
description = "This is a test keystore that uses PBKDF2 to secure the secret",
|
||
|
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
||
|
|
||
|
importKeystoresBody = KeystoresAndSlashingProtection(
|
||
|
keystores: @[newKeystore],
|
||
|
passwords: @[password],
|
||
|
slashing_protection: SPDIR())
|
||
|
|
||
|
deleteKeysBody = DeleteKeystoresBody(
|
||
|
pubkeys: @[privateKey.toPubKey.toPubKey])
|
||
|
|
||
|
suite "ListKeys requests" & preset():
|
||
|
asyncTest "Correct token provided" & preset():
|
||
|
let
|
||
|
filesystemKeystores = sorted(listValidators(validatorsDir, secretsDir))
|
||
|
apiKeystores = sorted((await client.listKeys(correctTokenValue)).data)
|
||
|
|
||
|
check filesystemKeystores == apiKeystores
|
||
|
|
||
|
asyncTest "Missing Authorization header" & preset():
|
||
|
let
|
||
|
response = await client.listKeysPlain()
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
||
|
|
||
|
asyncTest "Invalid Authorization Header" & preset():
|
||
|
let
|
||
|
response = await client.listKeysPlain(
|
||
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
||
|
|
||
|
asyncTest "Invalid Authorization Token" & preset():
|
||
|
let
|
||
|
response = await client.listKeysPlain(
|
||
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
||
|
|
||
|
expect RestError:
|
||
|
let keystores = await client.listKeys("Invalid Token")
|
||
|
|
||
|
suite "ImportKeystores requests" & preset():
|
||
|
asyncTest "Missing Authorization header" & preset():
|
||
|
let
|
||
|
response = await client.importKeystoresPlain(importKeystoresBody)
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
||
|
|
||
|
asyncTest "Invalid Authorization Header" & preset():
|
||
|
let
|
||
|
response = await client.importKeystoresPlain(
|
||
|
importKeystoresBody,
|
||
|
extraHeaders = @[("Authorization", "Basic XYZ")])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
||
|
|
||
|
asyncTest "Invalid Authorization Token" & preset():
|
||
|
let
|
||
|
response = await client.importKeystoresPlain(
|
||
|
importKeystoresBody,
|
||
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
||
|
|
||
|
suite "DeleteKeys requests" & preset():
|
||
|
asyncTest "Deleting not existing key" & preset():
|
||
|
let
|
||
|
response = await client.deleteKeysPlain(
|
||
|
deleteKeysBody,
|
||
|
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 200
|
||
|
responseJson["data"][0]["status"].getStr() == "not_found"
|
||
|
responseJson["data"][0]["message"].getStr() == ""
|
||
|
|
||
|
asyncTest "Missing Authorization header" & preset():
|
||
|
let
|
||
|
response = await client.deleteKeysPlain(deleteKeysBody)
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
||
|
|
||
|
asyncTest "Invalid Authorization Header" & preset():
|
||
|
let
|
||
|
response = await client.deleteKeysPlain(
|
||
|
deleteKeysBody,
|
||
|
extraHeaders = @[("Authorization", "Basic XYZ")])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
||
|
|
||
|
asyncTest "Invalid Authorization Token" & preset():
|
||
|
let
|
||
|
response = await client.deleteKeysPlain(
|
||
|
deleteKeysBody,
|
||
|
extraHeaders = @[("Authorization", "Bearer XYZ")])
|
||
|
responseJson = Json.decode(response.data, JsonNode)
|
||
|
|
||
|
check:
|
||
|
response.status == 401
|
||
|
responseJson["code"].getStr() == "401"
|
||
|
responseJson["message"].getStr() == InvalidAuthorization
|
||
|
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
||
|
|
||
|
bnStatus = BeaconNodeStatus.Stopping
|
||
|
|
||
|
proc main() {.async.} =
|
||
|
asyncSpawn runTests()
|
||
|
startSingleNodeNetwork()
|
||
|
|
||
|
waitFor main()
|