mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-24 03:00:25 +00:00
apply jwt auth to rpcHttpServer and update jwt auth of rpcWebsocketServer
fixes #967
This commit is contained in:
parent
6cfaaf5b45
commit
e6938af437
@ -168,9 +168,34 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
||||
info "metrics", registry
|
||||
discard setTimer(Moment.fromNow(conf.logMetricsInterval.seconds), logMetrics)
|
||||
discard setTimer(Moment.fromNow(conf.logMetricsInterval.seconds), logMetrics)
|
||||
|
||||
# Provide JWT authentication handler for websockets
|
||||
let jwtKey = block:
|
||||
# Create or load shared secret
|
||||
let rc = nimbus.ctx.rng.jwtSharedSecret(conf)
|
||||
if rc.isErr:
|
||||
error "Failed create or load shared secret",
|
||||
msg = $(rc.unsafeError) # avoid side effects
|
||||
quit(QuitFailure)
|
||||
rc.value
|
||||
|
||||
# Provide JWT authentication handler for rpcHttpServer
|
||||
let httpJwtAuthHook = httpJwtAuth(jwtKey)
|
||||
|
||||
# Creating RPC Server
|
||||
if conf.rpcEnabled:
|
||||
nimbus.rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
|
||||
let enableAuthHook = conf.engineApiEnabled and
|
||||
conf.engineApiPort == conf.rpcPort
|
||||
|
||||
let hooks = if enableAuthHook:
|
||||
@[httpJwtAuthHook]
|
||||
else:
|
||||
@[]
|
||||
|
||||
nimbus.rpcServer = newRpcHttpServer(
|
||||
[initTAddress(conf.rpcAddress, conf.rpcPort)],
|
||||
authHooks = hooks
|
||||
)
|
||||
setupCommonRpc(nimbus.ethNode, conf, nimbus.rpcServer)
|
||||
|
||||
# Enable RPC APIs based on RPC flags and protocol flags
|
||||
@ -188,23 +213,24 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
||||
|
||||
nimbus.rpcServer.start()
|
||||
|
||||
# Provide JWT authentication handler for websockets
|
||||
let jwtHook = block:
|
||||
# Create or load shared secret
|
||||
let rc = nimbus.ctx.rng.jwtSharedSecret(conf)
|
||||
if rc.isErr:
|
||||
error "Failed create or load shared secret",
|
||||
msg = $(rc.unsafeError) # avoid side effects
|
||||
quit(QuitFailure)
|
||||
# Authentcation handler constructor
|
||||
some(rc.value.jwtAuthHandler)
|
||||
# Provide JWT authentication handler for rpcWebsocketServer
|
||||
let wsJwtAuthHook = wsJwtAuth(jwtKey)
|
||||
|
||||
# Creating Websocket RPC Server
|
||||
if conf.wsEnabled:
|
||||
let enableAuthHook = conf.engineApiWsEnabled and
|
||||
conf.engineApiWsPort == conf.wsPort
|
||||
|
||||
let hooks = if enableAuthHook:
|
||||
@[wsJwtAuthHook]
|
||||
else:
|
||||
@[]
|
||||
|
||||
# Construct server object
|
||||
nimbus.wsRpcServer = newRpcWebSocketServer(
|
||||
initTAddress(conf.wsAddress, conf.wsPort),
|
||||
authHandler = jwtHook)
|
||||
authHooks = hooks
|
||||
)
|
||||
setupCommonRpc(nimbus.ethNode, conf, nimbus.wsRpcServer)
|
||||
|
||||
# Enable Websocket RPC APIs based on RPC flags and protocol flags
|
||||
@ -260,9 +286,10 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
||||
|
||||
if conf.engineApiEnabled:
|
||||
if conf.engineApiPort != conf.rpcPort:
|
||||
nimbus.engineApiServer = newRpcHttpServer([
|
||||
initTAddress(conf.engineApiAddress, conf.engineApiPort)
|
||||
])
|
||||
nimbus.engineApiServer = newRpcHttpServer(
|
||||
[initTAddress(conf.engineApiAddress, conf.engineApiPort)],
|
||||
authHooks = @[httpJwtAuthHook]
|
||||
)
|
||||
setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiServer)
|
||||
setupEthRpc(nimbus.ethNode, nimbus.ctx, chainDB, nimbus.txPool, nimbus.engineApiServer)
|
||||
nimbus.engineApiServer.start()
|
||||
@ -275,7 +302,8 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
||||
if conf.engineApiWsPort != conf.wsPort:
|
||||
nimbus.engineApiWsServer = newRpcWebSocketServer(
|
||||
initTAddress(conf.engineApiWsAddress, conf.engineApiWsPort),
|
||||
authHandler = jwtHook)
|
||||
authHooks = @[wsJwtAuthHook]
|
||||
)
|
||||
setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiWsServer)
|
||||
setupEthRpc(nimbus.ethNode, nimbus.ctx, chainDB, nimbus.txPool, nimbus.engineApiWsServer)
|
||||
nimbus.engineApiWsServer.start()
|
||||
|
@ -17,10 +17,10 @@ import
|
||||
bearssl/rand,
|
||||
chronicles,
|
||||
chronos,
|
||||
chronos/apps/http/httptable,
|
||||
json_rpc/servers/websocketserver,
|
||||
chronos/apps/http/[httptable, httpserver],
|
||||
json_rpc/rpcserver,
|
||||
httputils,
|
||||
websock/types as ws,
|
||||
websock/websock as ws,
|
||||
nimcrypto/[hmac, utils],
|
||||
stew/[byteutils, objects, results],
|
||||
../config
|
||||
@ -40,10 +40,6 @@ const
|
||||
32
|
||||
|
||||
type
|
||||
JwtAuthHandler* = ##\
|
||||
## Generic authentication handler, also provided by the web-socket server.
|
||||
RpcWebSocketServerAuth
|
||||
|
||||
JwtSharedKey* = ##\
|
||||
## Convenience type, needed quite often
|
||||
distinct array[jwtMinSecretLen,byte]
|
||||
@ -183,7 +179,7 @@ proc fromHex*(key: var JwtSharedKey, src: string): Result[void,JwtError] =
|
||||
except ValueError:
|
||||
err(jwtKeyInvalidHexString)
|
||||
|
||||
proc jwtGenSecret*(rng: ref HmacDrbgContext): JwtGenSecret =
|
||||
proc jwtGenSecret*(rng: ref rand.HmacDrbgContext): JwtGenSecret =
|
||||
## Standard shared key random generator. If a fixed key is needed, a
|
||||
## function like
|
||||
## ::
|
||||
@ -255,37 +251,57 @@ proc jwtSharedSecret*(rndSecret: JwtGenSecret; config: NimbusConf):
|
||||
except ValueError:
|
||||
return err(jwtKeyInvalidHexString)
|
||||
|
||||
proc jwtSharedSecret*(rng: ref HmacDrbgContext; config: NimbusConf):
|
||||
proc jwtSharedSecret*(rng: ref rand.HmacDrbgContext; config: NimbusConf):
|
||||
Result[JwtSharedKey, JwtError]
|
||||
{.gcsafe, raises: [Defect,JwtExcept].} =
|
||||
## Variant of `jwtSharedSecret()` with explicit random generator argument.
|
||||
safeExecutor("jwtSharedSecret"):
|
||||
result = rng.jwtGenSecret.jwtSharedSecret(config)
|
||||
|
||||
|
||||
proc jwtAuthHandler*(key: JwtSharedKey): JwtAuthHandler =
|
||||
## Returns a JWT authentication handler that can be used with an HTTP header
|
||||
## based call back system as the web socket server.
|
||||
##
|
||||
## The argument `key` is captured by the session handler for JWT
|
||||
## authentication. The function `jwtSharedSecret()` provides such a key.
|
||||
result = proc(req: HttpTable): Result[void,(HttpCode,string)] {.gcsafe.} =
|
||||
let auth = req.getString("Authorization","?")
|
||||
proc httpJwtAuth*(key: JwtSharedKey): HttpAuthHook =
|
||||
proc handler(req: HttpRequestRef): Future[HttpResponseRef] {.async.} =
|
||||
let auth = req.headers.getString("Authorization", "?")
|
||||
if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0:
|
||||
return err((Http403, "Missing Token"))
|
||||
return await req.respond(Http403, "Missing authorization token")
|
||||
|
||||
let rc = auth[7..^1].strip.verifyTokenHS256(key)
|
||||
if rc.isOk:
|
||||
return ok()
|
||||
return HttpResponseRef(nil)
|
||||
|
||||
debug "Could not authenticate",
|
||||
error = rc.error
|
||||
error = rc.error
|
||||
|
||||
case rc.error:
|
||||
of jwtTokenValidationError, jwtMethodUnsupported:
|
||||
return err((Http401, "Unauthorized"))
|
||||
return await req.respond(Http401, "Unauthorized access")
|
||||
else:
|
||||
return err((Http403, "Malformed Token"))
|
||||
return await req.respond(Http403, "Malformed token")
|
||||
|
||||
result = HttpAuthHook(handler)
|
||||
|
||||
proc wsJwtAuth*(key: JwtSharedKey): WsAuthHook =
|
||||
proc handler(req: ws.HttpRequest): Future[bool] {.async.} =
|
||||
let auth = req.headers.getString("Authorization", "?")
|
||||
if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0:
|
||||
await req.sendResponse(code = Http403, data = "Missing authorization token")
|
||||
return false
|
||||
|
||||
let rc = auth[7..^1].strip.verifyTokenHS256(key)
|
||||
if rc.isOk:
|
||||
return true
|
||||
|
||||
debug "Could not authenticate",
|
||||
error = rc.error
|
||||
|
||||
case rc.error:
|
||||
of jwtTokenValidationError, jwtMethodUnsupported:
|
||||
await req.sendResponse(code = Http403, data = "Unauthorized access")
|
||||
else:
|
||||
await req.sendResponse(code = Http403, data = "Malformed token")
|
||||
|
||||
return false
|
||||
|
||||
result = WsAuthHook(handler)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
|
@ -18,13 +18,16 @@ import
|
||||
./replay/pp,
|
||||
confutils/defs,
|
||||
chronicles,
|
||||
chronos/apps/http/httpserver,
|
||||
chronos/apps/http/httpclient as chronoshttpclient,
|
||||
chronos/apps/http/httptable,
|
||||
eth/[common, keys, p2p],
|
||||
json_rpc/rpcserver,
|
||||
nimcrypto/[hmac, utils],
|
||||
stew/results,
|
||||
stint,
|
||||
unittest2
|
||||
unittest2,
|
||||
graphql,
|
||||
graphql/[httpserver, httpclient]
|
||||
|
||||
type
|
||||
UnGuardedKey =
|
||||
@ -113,6 +116,32 @@ proc getHttpAuthReqHeader2(secret: JwtSharedKey; time: uint64): HttpTable =
|
||||
let bearer = secret.UnGuardedKey.getSignedToken2($getIatToken(time))
|
||||
result.add("aUtHoRiZaTiOn", "Bearer " & bearer)
|
||||
|
||||
proc createServer(serverAddress: TransportAddress, authHooks: seq[HttpAuthHook] = @[]): GraphqlHttpServerRef =
|
||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
var ctx = GraphqlRef.new()
|
||||
|
||||
const schema = """type Query {name: String}"""
|
||||
let r = ctx.parseSchema(schema)
|
||||
if r.isErr:
|
||||
debugEcho r.error
|
||||
return
|
||||
|
||||
let res = GraphqlHttpServerRef.new(
|
||||
graphql = ctx,
|
||||
address = serverAddress,
|
||||
socketFlags = socketFlags,
|
||||
authHooks = authHooks
|
||||
)
|
||||
|
||||
if res.isErr():
|
||||
debugEcho res.error
|
||||
return
|
||||
|
||||
res.get()
|
||||
|
||||
proc setupClient(address: TransportAddress): GraphqlHttpClientRef =
|
||||
GraphqlHttpClientRef.new(address, secure = false).get()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Test Runners
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -224,9 +253,15 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
||||
secret = fakeKey.fakeGenSecret.jwtSharedSecret(config)
|
||||
|
||||
# The wrapper contains the handler function with the captured shared key
|
||||
handler = secret.value.jwtAuthHandler
|
||||
authHook = secret.value.httpJwtAuth
|
||||
|
||||
const
|
||||
serverAddress = initTAddress("127.0.0.1:8547")
|
||||
query = """{ __type(name: "ID") { kind }}"""
|
||||
|
||||
suite "EngineAuth: Http/rpc authentication mechanics":
|
||||
let server = createServer(serverAddress, @[authHook])
|
||||
server.start()
|
||||
|
||||
test &"JSW/HS256 authentication using shared secret file {fileInfo}":
|
||||
# Just to make sure that we made a proper choice. Typically, all
|
||||
@ -249,11 +284,18 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
||||
setTraceLevel()
|
||||
|
||||
# Run http authorisation request
|
||||
let htCode = req.handler
|
||||
noisy.say "***", "result",
|
||||
" htCode=", htCode
|
||||
let client = setupClient(serverAddress)
|
||||
let res = waitFor client.sendRequest(query, req.toList)
|
||||
check res.isOk
|
||||
if res.isErr:
|
||||
noisy.say "***", res.error
|
||||
return
|
||||
|
||||
let resp = res.get()
|
||||
check resp.status == 200
|
||||
check resp.reason == "OK"
|
||||
check resp.response == """{"data":{"__type":{"kind":"SCALAR"}}}"""
|
||||
|
||||
check htCode.isOk
|
||||
setErrorLevel()
|
||||
|
||||
test &"JSW/HS256, ditto with protected header variant":
|
||||
@ -268,13 +310,22 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
||||
setTraceLevel()
|
||||
|
||||
# Run http authorisation request
|
||||
let htCode = req.handler
|
||||
noisy.say "***", "result",
|
||||
" htCode=", htCode
|
||||
let client = setupClient(serverAddress)
|
||||
let res = waitFor client.sendRequest(query, req.toList)
|
||||
check res.isOk
|
||||
if res.isErr:
|
||||
noisy.say "***", res.error
|
||||
return
|
||||
|
||||
let resp = res.get()
|
||||
check resp.status == 200
|
||||
check resp.reason == "OK"
|
||||
check resp.response == """{"data":{"__type":{"kind":"SCALAR"}}}"""
|
||||
|
||||
check htCode.isOk
|
||||
setErrorLevel()
|
||||
|
||||
waitFor server.closeWait()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main function(s)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
Loading…
x
Reference in New Issue
Block a user