hive: engine simulator add jwt auth test cases
This commit is contained in:
parent
76e438da2a
commit
a48f69a89a
|
@ -0,0 +1,109 @@
|
||||||
|
import
|
||||||
|
std/[base64, times, strutils],
|
||||||
|
test_env,
|
||||||
|
unittest2,
|
||||||
|
chronicles,
|
||||||
|
nimcrypto/[hmac, utils],
|
||||||
|
json_rpc/[rpcclient],
|
||||||
|
./types
|
||||||
|
|
||||||
|
# JWT Authentication Related
|
||||||
|
const
|
||||||
|
defaultJwtTokenSecretBytes = "secretsecretsecretsecretsecretse"
|
||||||
|
maxTimeDriftSeconds = 5'i64
|
||||||
|
defaultProtectedHeader = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
|
||||||
|
|
||||||
|
proc base64urlEncode(x: auto): string =
|
||||||
|
base64.encode(x, safe = true).replace("=", "")
|
||||||
|
|
||||||
|
proc prepareAuthCallToken(secret: string, time: int64): string =
|
||||||
|
let key = cast[seq[byte]](secret)
|
||||||
|
let payload = """{"iat": $1}""" % [$time]
|
||||||
|
let token = defaultProtectedHeader & "." & payload.base64urlEncode
|
||||||
|
let sig = base64urlEncode(sha256.hmac(key, token).data)
|
||||||
|
token & "." & sig
|
||||||
|
|
||||||
|
proc getClient(t: TestEnv, token: string): RpcHttpClient =
|
||||||
|
proc authHeaders(): seq[(string, string)] =
|
||||||
|
@[("Authorization", "Bearer " & token)]
|
||||||
|
|
||||||
|
let client = newRpcHttpClient(getHeaders = authHeaders)
|
||||||
|
waitFor client.connect("localhost", t.conf.rpcPort, false)
|
||||||
|
return client
|
||||||
|
|
||||||
|
template genAuthTest(procName: untyped, timeDriftSeconds: int64, customAuthSecretBytes: string, authOK: bool) =
|
||||||
|
proc procName(t: TestEnv): TestStatus =
|
||||||
|
result = TestStatus.OK
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
var
|
||||||
|
# All test cases send a simple TransitionConfigurationV1 to check the Authentication mechanism (JWT)
|
||||||
|
tConf = TransitionConfigurationV1(
|
||||||
|
terminalTotalDifficulty: t.ttd
|
||||||
|
)
|
||||||
|
testSecret = customAuthSecretBytes
|
||||||
|
testTime = getTime().toUnix
|
||||||
|
|
||||||
|
if testSecret.len == 0:
|
||||||
|
testSecret = defaultJwtTokenSecretBytes
|
||||||
|
|
||||||
|
if timeDriftSeconds != 0:
|
||||||
|
testTime = testTime + timeDriftSeconds
|
||||||
|
|
||||||
|
let token = prepareAuthCallToken(testSecret, testTime)
|
||||||
|
let client = getClient(t, token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
discard waitFor client.call("engine_exchangeTransitionConfigurationV1", %[%tConf])
|
||||||
|
testCond authOk:
|
||||||
|
error "Authentication was supposed to fail authentication but passed"
|
||||||
|
except CatchableError:
|
||||||
|
testCond not authOk:
|
||||||
|
error "Authentication was supposed to pass authentication but failed"
|
||||||
|
|
||||||
|
genAuthTest(authTest1, 0'i64, "", true)
|
||||||
|
genAuthTest(authTest2, 0'i64, "secretsecretsecretsecretsecrets", false)
|
||||||
|
genAuthTest(authTest3, 0'i64, "\0secretsecretsecretsecretsecretse", false)
|
||||||
|
genAuthTest(authTest4, -1 - maxTimeDriftSeconds, "", false)
|
||||||
|
genAuthTest(authTest5, 1 - maxTimeDriftSeconds, "", true)
|
||||||
|
genAuthTest(authTest6, maxTimeDriftSeconds + 1, "", false)
|
||||||
|
genAuthTest(authTest7, maxTimeDriftSeconds - 1, "", true)
|
||||||
|
|
||||||
|
# JWT Authentication Tests
|
||||||
|
const authTestList* = [
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: No time drift, correct secret",
|
||||||
|
run: authTest1,
|
||||||
|
enableAuth: true
|
||||||
|
),
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: No time drift, incorrect secret (shorter)",
|
||||||
|
run: authTest2,
|
||||||
|
enableAuth: true
|
||||||
|
),
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: No time drift, incorrect secret (longer)",
|
||||||
|
run: authTest3,
|
||||||
|
enableAuth: true
|
||||||
|
),
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: Negative time drift, exceeding limit, correct secret",
|
||||||
|
run: authTest4,
|
||||||
|
enableAuth: true
|
||||||
|
),
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: Negative time drift, within limit, correct secret",
|
||||||
|
run: authTest5,
|
||||||
|
enableAuth: true
|
||||||
|
),
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: Positive time drift, exceeding limit, correct secret",
|
||||||
|
run: authTest6,
|
||||||
|
enableAuth: true
|
||||||
|
),
|
||||||
|
TestSpec(
|
||||||
|
name: "JWT Authentication: Positive time drift, within limit, correct secret",
|
||||||
|
run: authTest7,
|
||||||
|
enableAuth: true
|
||||||
|
)
|
||||||
|
]
|
|
@ -7,7 +7,7 @@ import
|
||||||
web3/engine_api_types,
|
web3/engine_api_types,
|
||||||
json_rpc/rpcclient,
|
json_rpc/rpcclient,
|
||||||
../../../nimbus/merge/mergeutils,
|
../../../nimbus/merge/mergeutils,
|
||||||
../../../nimbus/[debug, constants],
|
../../../nimbus/[constants],
|
||||||
./engine_client
|
./engine_client
|
||||||
|
|
||||||
# Consensus Layer Client Mock used to sync the Execution Clients once the TTD has been reached
|
# Consensus Layer Client Mock used to sync the Execution Clients once the TTD has been reached
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
import
|
import
|
||||||
test_env,
|
"."/[types, test_env, engine_tests, auths_tests],
|
||||||
engine_tests,
|
|
||||||
unittest2,
|
unittest2,
|
||||||
../sim_utils
|
../sim_utils
|
||||||
|
|
||||||
|
proc combineTests(): seq[TestSpec] =
|
||||||
|
result = @engineTestList
|
||||||
|
result.add @authTestList
|
||||||
|
|
||||||
|
const testList = combineTests()
|
||||||
|
|
||||||
proc main() =
|
proc main() =
|
||||||
var stat: SimStat
|
var stat: SimStat
|
||||||
let start = getTime()
|
let start = getTime()
|
||||||
|
|
||||||
for x in engineTestList:
|
for x in testList:
|
||||||
var t = setupELClient(x.chainFile)
|
var t = setupELClient(x.chainFile, x.enableAuth)
|
||||||
t.setRealTTD(x.ttd)
|
t.setRealTTD(x.ttd)
|
||||||
if x.slotsToFinalized != 0:
|
if x.slotsToFinalized != 0:
|
||||||
t.slotsToFinalized(x.slotsToFinalized)
|
t.slotsToFinalized(x.slotsToFinalized)
|
||||||
|
|
|
@ -1,61 +1,18 @@
|
||||||
import
|
import
|
||||||
std/tables,
|
std/tables,
|
||||||
test_env,
|
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
chronicles,
|
chronicles,
|
||||||
unittest2,
|
unittest2,
|
||||||
nimcrypto,
|
nimcrypto,
|
||||||
chronos,
|
chronos,
|
||||||
./helper,
|
"."/[test_env, helper, types],
|
||||||
../../../nimbus/transaction,
|
../../../nimbus/transaction,
|
||||||
../../../nimbus/rpc/rpc_types,
|
../../../nimbus/rpc/rpc_types,
|
||||||
../../../nimbus/merge/mergeutils
|
../../../nimbus/merge/mergeutils
|
||||||
|
|
||||||
type
|
|
||||||
TestSpec* = object
|
|
||||||
name*: string
|
|
||||||
run*: proc(t: TestEnv): TestStatus
|
|
||||||
ttd*: int64
|
|
||||||
chainFile*: string
|
|
||||||
slotsToFinalized*: int
|
|
||||||
slotsToSafe*: int
|
|
||||||
|
|
||||||
const
|
const
|
||||||
prevRandaoContractAddr = hexToByteArray[20]("0000000000000000000000000000000000000316")
|
prevRandaoContractAddr = hexToByteArray[20]("0000000000000000000000000000000000000316")
|
||||||
|
|
||||||
template testCond(expr: untyped) =
|
|
||||||
if not (expr):
|
|
||||||
when result is bool:
|
|
||||||
return false
|
|
||||||
else:
|
|
||||||
return TestStatus.Failed
|
|
||||||
|
|
||||||
template testCond(expr, body: untyped) =
|
|
||||||
if not (expr):
|
|
||||||
body
|
|
||||||
when result is bool:
|
|
||||||
return false
|
|
||||||
else:
|
|
||||||
return TestStatus.Failed
|
|
||||||
|
|
||||||
proc `$`(x: Option[Hash256]): string =
|
|
||||||
if x.isNone:
|
|
||||||
"none"
|
|
||||||
else:
|
|
||||||
$x.get()
|
|
||||||
|
|
||||||
proc `$`(x: Option[BlockHash]): string =
|
|
||||||
if x.isNone:
|
|
||||||
"none"
|
|
||||||
else:
|
|
||||||
$x.get()
|
|
||||||
|
|
||||||
proc `$`(x: Option[PayloadID]): string =
|
|
||||||
if x.isNone:
|
|
||||||
"none"
|
|
||||||
else:
|
|
||||||
x.get().toHex
|
|
||||||
|
|
||||||
proc `==`(a: Option[BlockHash], b: Option[Hash256]): bool =
|
proc `==`(a: Option[BlockHash], b: Option[Hash256]): bool =
|
||||||
if a.isNone and b.isNone:
|
if a.isNone and b.isNone:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -20,6 +20,7 @@ import
|
||||||
rpc/p2p,
|
rpc/p2p,
|
||||||
rpc/engine_api,
|
rpc/engine_api,
|
||||||
rpc/debug,
|
rpc/debug,
|
||||||
|
rpc/jwt_auth,
|
||||||
sync/protocol,
|
sync/protocol,
|
||||||
utils/tx_pool
|
utils/tx_pool
|
||||||
],
|
],
|
||||||
|
@ -38,14 +39,14 @@ type
|
||||||
EthBlockHeader* = common.BlockHeader
|
EthBlockHeader* = common.BlockHeader
|
||||||
|
|
||||||
TestEnv* = ref object
|
TestEnv* = ref object
|
||||||
conf: NimbusConf
|
conf*: NimbusConf
|
||||||
ctx: EthContext
|
ctx: EthContext
|
||||||
ethNode: EthereumNode
|
ethNode: EthereumNode
|
||||||
chainDB: BaseChainDB
|
chainDB: BaseChainDB
|
||||||
chainRef: Chain
|
chainRef: Chain
|
||||||
rpcServer: RpcSocketServer
|
rpcServer: RpcHttpServer
|
||||||
sealingEngine: SealingEngineRef
|
sealingEngine: SealingEngineRef
|
||||||
rpcClient*: RpcSocketClient
|
rpcClient*: RpcHttpClient
|
||||||
gHeader*: EthBlockHeader
|
gHeader*: EthBlockHeader
|
||||||
ttd*: DifficultyInt
|
ttd*: DifficultyInt
|
||||||
clMock*: CLMocker
|
clMock*: CLMocker
|
||||||
|
@ -69,8 +70,9 @@ const
|
||||||
# This is the account that sends vault funding transactions.
|
# This is the account that sends vault funding transactions.
|
||||||
vaultAccountAddr* = hexToByteArray[20]("0xcf49fda3be353c69b41ed96333cd24302da4556f")
|
vaultAccountAddr* = hexToByteArray[20]("0xcf49fda3be353c69b41ed96333cd24302da4556f")
|
||||||
vaultKeyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
|
vaultKeyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
|
||||||
|
jwtSecret = "0x7365637265747365637265747365637265747365637265747365637265747365"
|
||||||
|
|
||||||
proc setupELClient*(t: TestEnv, chainFile: string) =
|
proc setupELClient*(t: TestEnv, chainFile: string, enableAuth: bool) =
|
||||||
if chainFile.len > 0:
|
if chainFile.len > 0:
|
||||||
# disable clique if we are using PoW chain
|
# disable clique if we are using PoW chain
|
||||||
t.conf.networkParams.config.poaEngine = false
|
t.conf.networkParams.config.poaEngine = false
|
||||||
|
@ -93,7 +95,18 @@ proc setupELClient*(t: TestEnv, chainFile: string) =
|
||||||
initializeEmptyDb(t.chainDB)
|
initializeEmptyDb(t.chainDB)
|
||||||
let txPool = TxPoolRef.new(t.chainDB, t.conf.engineSigner)
|
let txPool = TxPoolRef.new(t.chainDB, t.conf.engineSigner)
|
||||||
|
|
||||||
t.rpcServer = newRpcSocketServer(["localhost:" & $t.conf.rpcPort])
|
var key: JwtSharedKey
|
||||||
|
let kr = key.fromHex(jwtSecret)
|
||||||
|
if kr.isErr:
|
||||||
|
echo "JWT SECRET ERROR: ", kr.error
|
||||||
|
quit(QuitFailure)
|
||||||
|
|
||||||
|
let hooks = if enableAuth:
|
||||||
|
@[httpJwtAuth(key)]
|
||||||
|
else:
|
||||||
|
@[]
|
||||||
|
|
||||||
|
t.rpcServer = newRpcHttpServer(["localhost:" & $t.conf.rpcPort], hooks)
|
||||||
t.sealingEngine = SealingEngineRef.new(
|
t.sealingEngine = SealingEngineRef.new(
|
||||||
t.chainRef, t.ctx, t.conf.engineSigner,
|
t.chainRef, t.ctx, t.conf.engineSigner,
|
||||||
txPool, EngineStopped
|
txPool, EngineStopped
|
||||||
|
@ -107,13 +120,13 @@ proc setupELClient*(t: TestEnv, chainFile: string) =
|
||||||
if chainFile.len > 0:
|
if chainFile.len > 0:
|
||||||
if not importRlpBlock(chainFolder / chainFile, t.chainDB):
|
if not importRlpBlock(chainFolder / chainFile, t.chainDB):
|
||||||
quit(QuitFailure)
|
quit(QuitFailure)
|
||||||
else:
|
elif not enableAuth:
|
||||||
t.sealingEngine.start()
|
t.sealingEngine.start()
|
||||||
|
|
||||||
t.rpcServer.start()
|
t.rpcServer.start()
|
||||||
|
|
||||||
t.rpcClient = newRpcSocketClient()
|
t.rpcClient = newRpcHttpClient()
|
||||||
waitFor t.rpcClient.connect("localhost", t.conf.rpcPort)
|
waitFor t.rpcClient.connect("localhost", t.conf.rpcPort, false)
|
||||||
t.gHeader = toGenesisHeader(t.conf.networkParams)
|
t.gHeader = toGenesisHeader(t.conf.networkParams)
|
||||||
|
|
||||||
let kRes = PrivateKey.fromHex(vaultKeyHex)
|
let kRes = PrivateKey.fromHex(vaultKeyHex)
|
||||||
|
@ -123,16 +136,16 @@ proc setupELClient*(t: TestEnv, chainFile: string) =
|
||||||
|
|
||||||
t.vaultKey = kRes.get
|
t.vaultKey = kRes.get
|
||||||
|
|
||||||
proc setupELClient*(chainFile: string): TestEnv =
|
proc setupELClient*(chainFile: string, enableAuth: bool): TestEnv =
|
||||||
result = TestEnv(
|
result = TestEnv(
|
||||||
conf: makeConfig(@["--engine-signer:658bdf435d810c91414ec09147daa6db62406379", "--custom-network:" & genesisFile])
|
conf: makeConfig(@["--engine-signer:658bdf435d810c91414ec09147daa6db62406379", "--custom-network:" & genesisFile])
|
||||||
)
|
)
|
||||||
setupELClient(result, chainFile)
|
setupELClient(result, chainFile, enableAuth)
|
||||||
|
|
||||||
proc stopELClient*(t: TestEnv) =
|
proc stopELClient*(t: TestEnv) =
|
||||||
waitFor t.rpcClient.close()
|
waitFor t.rpcClient.close()
|
||||||
waitFor t.sealingEngine.stop()
|
waitFor t.sealingEngine.stop()
|
||||||
t.rpcServer.stop()
|
#waitFor t.rpcServer.stop()
|
||||||
waitFor t.rpcServer.closeWait()
|
waitFor t.rpcServer.closeWait()
|
||||||
|
|
||||||
# TTD is the value specified in the TestSpec + Genesis.Difficulty
|
# TTD is the value specified in the TestSpec + Genesis.Difficulty
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import
|
||||||
|
std/options,
|
||||||
|
test_env,
|
||||||
|
unittest2,
|
||||||
|
web3/ethtypes,
|
||||||
|
../../../nimbus/merge/mergeutils
|
||||||
|
|
||||||
|
export ethtypes
|
||||||
|
|
||||||
|
type
|
||||||
|
TestSpec* = object
|
||||||
|
name*: string
|
||||||
|
run*: proc(t: TestEnv): TestStatus
|
||||||
|
ttd*: int64
|
||||||
|
chainFile*: string
|
||||||
|
slotsToFinalized*: int
|
||||||
|
slotsToSafe*: int
|
||||||
|
enableAuth*: bool
|
||||||
|
|
||||||
|
template testCond*(expr: untyped) =
|
||||||
|
if not (expr):
|
||||||
|
when result is bool:
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
return TestStatus.Failed
|
||||||
|
|
||||||
|
template testCond*(expr, body: untyped) =
|
||||||
|
if not (expr):
|
||||||
|
body
|
||||||
|
when result is bool:
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
return TestStatus.Failed
|
||||||
|
|
||||||
|
proc `$`*(x: Option[Hash256]): string =
|
||||||
|
if x.isNone:
|
||||||
|
"none"
|
||||||
|
else:
|
||||||
|
$x.get()
|
||||||
|
|
||||||
|
proc `$`*(x: Option[BlockHash]): string =
|
||||||
|
if x.isNone:
|
||||||
|
"none"
|
||||||
|
else:
|
||||||
|
$x.get()
|
||||||
|
|
||||||
|
proc `$`*(x: Option[PayloadID]): string =
|
||||||
|
if x.isNone:
|
||||||
|
"none"
|
||||||
|
else:
|
||||||
|
x.get().toHex
|
Loading…
Reference in New Issue