@@ -164,10 +164,8 @@ def get_signed_operation(request: web.Request) -> SignedOperation:
164164 raise web .HTTPBadRequest (reason = "Invalid X-SignedOperation format" ) from error
165165
166166
167- async def authenticate_jwk (request : web .Request ) -> str :
168- signed_pubkey = get_signed_pubkey (request )
169- signed_operation = get_signed_operation (request )
170-
167+ def verify_signed_operation (signed_operation : SignedOperation , signed_pubkey : SignedPubKeyHeader ) -> str :
168+ """Verify that the operation is signed by the ephemeral key authorized by the wallet."""
171169 if signed_pubkey .content .json_web_key .verify (
172170 data = signed_operation .payload ,
173171 signature = signed_operation .signature ,
@@ -176,7 +174,21 @@ async def authenticate_jwk(request: web.Request) -> str:
176174 logger .debug ("Signature verified" )
177175 return signed_pubkey .content .address
178176 else :
179- raise web .HTTPUnauthorized (reason = "Signature could not verified" ) from None
177+ raise web .HTTPUnauthorized (reason = "Signature could not verified" )
178+
179+
180+ async def authenticate_jwk (request : web .Request ) -> str :
181+ """Authenticate a request using the X-SignedPubKey and X-SignedOperation headers."""
182+ signed_pubkey = get_signed_pubkey (request )
183+ signed_operation = get_signed_operation (request )
184+ return verify_signed_operation (signed_operation , signed_pubkey )
185+
186+
187+ async def authenicate_websocket_message (message ) -> str :
188+ """Authenticate a websocket message since JS cannot configure headers on WebSockets."""
189+ signed_pubkey = SignedPubKeyHeader .parse_obj (message ["X-SignedPubKey" ])
190+ signed_operation = SignedOperation .parse_obj (message ["X-SignedOperation" ])
191+ return verify_signed_operation (signed_operation , signed_pubkey )
180192
181193
182194def require_jwk_authentication (
@@ -219,24 +231,39 @@ def get_execution_or_404(ref: ItemHash, pool: VmPool) -> VmExecution:
219231 raise web .HTTPNotFound (body = f"No virtual machine with ref { ref } " )
220232
221233
222- @require_jwk_authentication
223- async def stream_logs (request : web .Request , authenticated_sender : str ) -> web .StreamResponse :
234+ async def stream_logs (request : web .Request ) -> web .StreamResponse :
235+ """Stream the logs of a VM.
236+
237+ The authentication method is slightly different because browsers do not
238+ allow Javascript to set headers in WebSocket requests.
239+ """
224240 vm_hash = get_itemhash_or_400 (request .match_info )
225241 pool : VmPool = request .app ["vm_pool" ]
226242 execution = get_execution_or_404 (vm_hash , pool = pool )
227243
228244 if execution .vm is None :
229245 raise web .HTTPBadRequest (body = f"VM { vm_hash } is not running" )
230246
231- if execution .message .address != authenticated_sender :
232- logger .debug (f"Unauthorized sender { authenticated_sender } for { vm_hash } " )
233- return web .Response (status = 401 , body = "Unauthorized sender" )
234-
235247 queue : asyncio .Queue = asyncio .Queue (maxsize = 1000 )
236248 try :
237249 ws = web .WebSocketResponse ()
238250 await ws .prepare (request )
251+
239252 try :
253+ # Authentication
254+ first_message = await ws .receive_json ()
255+ credentials = first_message ["auth" ]
256+ authenticated_sender = await authenicate_websocket_message (credentials )
257+
258+ if execution .message .address != authenticated_sender :
259+ logger .debug (f"Denied request to access logs by { authenticated_sender } on { vm_hash } " )
260+ return web .Response (status = 401 , body = "Unauthorized sender" )
261+ else :
262+ logger .debug (f"Accepted request to access logs by { authenticated_sender } on { vm_hash } " )
263+ await ws .send_json ({"status" : "failed" , "reason" : "unauthorized sender" })
264+
265+ await ws .send_json ({"status" : "connected" })
266+
240267 # Limit the number of queues per VM
241268 if len (execution .vm .fvm .log_queues ) > 20 :
242269 logger .warning ("Too many log queues, dropping the oldest one" )
0 commit comments