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,
|
||||
json_rpc/rpcclient,
|
||||
../../../nimbus/merge/mergeutils,
|
||||
../../../nimbus/[debug, constants],
|
||||
../../../nimbus/[constants],
|
||||
./engine_client
|
||||
|
||||
# Consensus Layer Client Mock used to sync the Execution Clients once the TTD has been reached
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import
|
||||
test_env,
|
||||
engine_tests,
|
||||
"."/[types, test_env, engine_tests, auths_tests],
|
||||
unittest2,
|
||||
../sim_utils
|
||||
|
||||
proc combineTests(): seq[TestSpec] =
|
||||
result = @engineTestList
|
||||
result.add @authTestList
|
||||
|
||||
const testList = combineTests()
|
||||
|
||||
proc main() =
|
||||
var stat: SimStat
|
||||
let start = getTime()
|
||||
|
||||
for x in engineTestList:
|
||||
var t = setupELClient(x.chainFile)
|
||||
for x in testList:
|
||||
var t = setupELClient(x.chainFile, x.enableAuth)
|
||||
t.setRealTTD(x.ttd)
|
||||
if x.slotsToFinalized != 0:
|
||||
t.slotsToFinalized(x.slotsToFinalized)
|
||||
|
|
|
@ -1,61 +1,18 @@
|
|||
import
|
||||
std/tables,
|
||||
test_env,
|
||||
stew/byteutils,
|
||||
chronicles,
|
||||
unittest2,
|
||||
nimcrypto,
|
||||
chronos,
|
||||
./helper,
|
||||
"."/[test_env, helper, types],
|
||||
../../../nimbus/transaction,
|
||||
../../../nimbus/rpc/rpc_types,
|
||||
../../../nimbus/merge/mergeutils
|
||||
|
||||
type
|
||||
TestSpec* = object
|
||||
name*: string
|
||||
run*: proc(t: TestEnv): TestStatus
|
||||
ttd*: int64
|
||||
chainFile*: string
|
||||
slotsToFinalized*: int
|
||||
slotsToSafe*: int
|
||||
|
||||
const
|
||||
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 =
|
||||
if a.isNone and b.isNone:
|
||||
return true
|
||||
|
|
|
@ -20,6 +20,7 @@ import
|
|||
rpc/p2p,
|
||||
rpc/engine_api,
|
||||
rpc/debug,
|
||||
rpc/jwt_auth,
|
||||
sync/protocol,
|
||||
utils/tx_pool
|
||||
],
|
||||
|
@ -38,14 +39,14 @@ type
|
|||
EthBlockHeader* = common.BlockHeader
|
||||
|
||||
TestEnv* = ref object
|
||||
conf: NimbusConf
|
||||
conf*: NimbusConf
|
||||
ctx: EthContext
|
||||
ethNode: EthereumNode
|
||||
chainDB: BaseChainDB
|
||||
chainRef: Chain
|
||||
rpcServer: RpcSocketServer
|
||||
rpcServer: RpcHttpServer
|
||||
sealingEngine: SealingEngineRef
|
||||
rpcClient*: RpcSocketClient
|
||||
rpcClient*: RpcHttpClient
|
||||
gHeader*: EthBlockHeader
|
||||
ttd*: DifficultyInt
|
||||
clMock*: CLMocker
|
||||
|
@ -69,8 +70,9 @@ const
|
|||
# This is the account that sends vault funding transactions.
|
||||
vaultAccountAddr* = hexToByteArray[20]("0xcf49fda3be353c69b41ed96333cd24302da4556f")
|
||||
vaultKeyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
|
||||
jwtSecret = "0x7365637265747365637265747365637265747365637265747365637265747365"
|
||||
|
||||
proc setupELClient*(t: TestEnv, chainFile: string) =
|
||||
proc setupELClient*(t: TestEnv, chainFile: string, enableAuth: bool) =
|
||||
if chainFile.len > 0:
|
||||
# disable clique if we are using PoW chain
|
||||
t.conf.networkParams.config.poaEngine = false
|
||||
|
@ -93,7 +95,18 @@ proc setupELClient*(t: TestEnv, chainFile: string) =
|
|||
initializeEmptyDb(t.chainDB)
|
||||
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.chainRef, t.ctx, t.conf.engineSigner,
|
||||
txPool, EngineStopped
|
||||
|
@ -107,13 +120,13 @@ proc setupELClient*(t: TestEnv, chainFile: string) =
|
|||
if chainFile.len > 0:
|
||||
if not importRlpBlock(chainFolder / chainFile, t.chainDB):
|
||||
quit(QuitFailure)
|
||||
else:
|
||||
elif not enableAuth:
|
||||
t.sealingEngine.start()
|
||||
|
||||
t.rpcServer.start()
|
||||
|
||||
t.rpcClient = newRpcSocketClient()
|
||||
waitFor t.rpcClient.connect("localhost", t.conf.rpcPort)
|
||||
t.rpcClient = newRpcHttpClient()
|
||||
waitFor t.rpcClient.connect("localhost", t.conf.rpcPort, false)
|
||||
t.gHeader = toGenesisHeader(t.conf.networkParams)
|
||||
|
||||
let kRes = PrivateKey.fromHex(vaultKeyHex)
|
||||
|
@ -123,16 +136,16 @@ proc setupELClient*(t: TestEnv, chainFile: string) =
|
|||
|
||||
t.vaultKey = kRes.get
|
||||
|
||||
proc setupELClient*(chainFile: string): TestEnv =
|
||||
proc setupELClient*(chainFile: string, enableAuth: bool): TestEnv =
|
||||
result = TestEnv(
|
||||
conf: makeConfig(@["--engine-signer:658bdf435d810c91414ec09147daa6db62406379", "--custom-network:" & genesisFile])
|
||||
)
|
||||
setupELClient(result, chainFile)
|
||||
setupELClient(result, chainFile, enableAuth)
|
||||
|
||||
proc stopELClient*(t: TestEnv) =
|
||||
waitFor t.rpcClient.close()
|
||||
waitFor t.sealingEngine.stop()
|
||||
t.rpcServer.stop()
|
||||
#waitFor t.rpcServer.stop()
|
||||
waitFor t.rpcServer.closeWait()
|
||||
|
||||
# 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