@@ -108,7 +108,7 @@ struct ShellHandle {
108108}
109109
110110type WsSink = futures_util:: stream:: SplitSink <
111- tokio_tungstenite:: WebSocketStream < tokio_tungstenite :: MaybeTlsStream < tokio:: net:: TcpStream > > ,
111+ tokio_tungstenite:: WebSocketStream < tokio_rustls :: client :: TlsStream < tokio:: net:: TcpStream > > ,
112112 Message ,
113113> ;
114114
@@ -198,7 +198,7 @@ where
198198 . unwrap_or_default ( )
199199 . as_millis ( ) ;
200200 let acp_sid = format ! ( "agentcore-{ts}" ) ;
201- let runtime_sid = format ! ( "oab-session-{ts}" ) ;
201+ let runtime_sid = format ! ( "oab-session-{ts:020}-{ts:013x }" ) ;
202202
203203 // Eagerly open shell + initialize the agent
204204 match self . open_shell ( & runtime_sid) . await {
@@ -376,6 +376,7 @@ where
376376 }
377377 }
378378
379+ #[ allow( dead_code) ]
379380 fn derive_runtime_session_id ( & self , params : & Value ) -> String {
380381 // Try to extract from sender_context in prompt blocks
381382 if let Some ( blocks) = params. get ( "prompt" ) . and_then ( |p| p. as_array ( ) ) {
@@ -414,11 +415,38 @@ where
414415 }
415416
416417 async fn open_shell ( & self , session_id : & str ) -> Result < ShellHandle > {
417- let request = build_signed_request ( & self . runtime_arn , session_id, & self . region ) . await ?;
418+ let ( request, host) = build_signed_request ( & self . runtime_arn , session_id, & self . region ) . await ?;
419+
420+ // Manual TLS connection — gives us full control, avoids connect_async host override
421+ let tcp = tokio:: net:: TcpStream :: connect ( format ! ( "{host}:443" ) )
422+ . await
423+ . map_err ( |e| anyhow ! ( "TCP connect to {host}:443 failed: {e}" ) ) ?;
424+
425+ let connector = tokio_tungstenite:: Connector :: Rustls ( std:: sync:: Arc :: new (
426+ rustls:: ClientConfig :: builder ( )
427+ . with_root_certificates ( rustls:: RootCertStore {
428+ roots : webpki_roots:: TLS_SERVER_ROOTS . to_vec ( ) ,
429+ } )
430+ . with_no_client_auth ( ) ,
431+ ) ) ;
432+
433+ let tls_stream = match connector {
434+ tokio_tungstenite:: Connector :: Rustls ( cfg) => {
435+ let domain = rustls:: pki_types:: ServerName :: try_from ( host. as_str ( ) )
436+ . map_err ( |e| anyhow ! ( "bad DNS: {e}" ) ) ?
437+ . to_owned ( ) ;
438+ tokio_rustls:: TlsConnector :: from ( cfg)
439+ . connect ( domain, tcp)
440+ . await
441+ . map_err ( |e| anyhow ! ( "TLS failed: {e}" ) ) ?
442+ }
443+ _ => unreachable ! ( ) ,
444+ } ;
418445
419- let ( ws_stream, _) = tokio_tungstenite:: connect_async ( request)
446+ // client_async performs the WebSocket upgrade using our exact request
447+ let ( ws_stream, _) = tokio_tungstenite:: client_async ( request, tls_stream)
420448 . await
421- . map_err ( |e| anyhow ! ( "WebSocket connect failed: {e}" ) ) ?;
449+ . map_err ( |e| anyhow ! ( "WebSocket upgrade failed: {e}" ) ) ?;
422450
423451 info ! ( session_id, "AgentCore shell connected" ) ;
424452
@@ -571,7 +599,7 @@ async fn build_signed_request(
571599 arn : & str ,
572600 session_id : & str ,
573601 region : & str ,
574- ) -> Result < http:: Request < ( ) > > {
602+ ) -> Result < ( http:: Request < ( ) > , String ) > {
575603 let config = aws_config:: defaults ( aws_config:: BehaviorVersion :: latest ( ) )
576604 . region ( aws_config:: Region :: new ( region. to_string ( ) ) )
577605 . load ( )
@@ -594,15 +622,18 @@ async fn build_signed_request(
594622 let hash = Sha256 :: digest ( session_id. as_bytes ( ) ) ;
595623 let shell_id = format ! ( "oab-{}" , hex:: encode( & hash[ ..8 ] ) ) ;
596624
625+ // Include session_id in query so it's covered by the presigned signature
597626 let query = format ! (
598- "shellId={shell_id}&X-Amzn-Bedrock-AgentCore-Runtime-Session-Id={}" ,
627+ "qualifier=DEFAULT& shellId={shell_id}&X-Amzn-Bedrock-AgentCore-Runtime-Session-Id={}" ,
599628 urlencoding:: encode( session_id) ,
600629 ) ;
601-
602630 let uri = format ! ( "https://{host}{path}?{query}" ) ;
603631
632+ // Use presigned URL (auth in query string) — avoids tungstenite header issues
604633 let mut settings = SigningSettings :: default ( ) ;
605634 settings. expires_in = Some ( std:: time:: Duration :: from_secs ( 300 ) ) ;
635+ settings. uri_path_normalization_mode =
636+ aws_sigv4:: http_request:: UriPathNormalizationMode :: Enabled ;
606637
607638 let signing_params = v4:: SigningParams :: builder ( )
608639 . identity ( & identity)
@@ -613,30 +644,28 @@ async fn build_signed_request(
613644 . build ( ) ?;
614645
615646 let headers = [ ( "host" , host. as_str ( ) ) ] ;
616-
617647 let signable = SignableRequest :: new ( "GET" , & uri, headers. into_iter ( ) , SignableBody :: empty ( ) ) ?;
618-
619648 let ( instructions, _sig) = sign ( signable, & signing_params. into ( ) ) ?. into_parts ( ) ;
620649
621- // Build presigned URL with auth in query params
622- let mut signed_query = query. clone ( ) ;
623- let ( _headers , params) = instructions. into_parts ( ) ;
650+ // Append SigV4 query params to build the presigned URL
651+ let mut presigned_query = query. clone ( ) ;
652+ let ( _hdrs , params) = instructions. into_parts ( ) ;
624653 for ( name, value) in params {
625- signed_query . push_str ( & format ! ( "&{name}={value}" ) ) ;
654+ presigned_query . push_str ( & format ! ( "&{name}={value}" ) ) ;
626655 }
627- let wss_uri = format ! ( "wss://{host}{path}?{signed_query}" ) ;
628656
629- let builder = http:: Request :: builder ( )
657+ let wss_uri = format ! ( "wss://{host}{path}?{presigned_query}" ) ;
658+
659+ // Build request with WebSocket upgrade headers (not signed, just protocol-required)
660+ let request = http:: Request :: builder ( )
630661 . method ( "GET" )
631662 . uri ( & wss_uri)
632663 . header ( "host" , & host)
633664 . header ( "connection" , "Upgrade" )
634665 . header ( "upgrade" , "websocket" )
635666 . header ( "sec-websocket-version" , "13" )
636- . header (
637- "sec-websocket-key" ,
638- tokio_tungstenite:: tungstenite:: handshake:: client:: generate_key ( ) ,
639- ) ;
667+ . header ( "sec-websocket-key" , tokio_tungstenite:: tungstenite:: handshake:: client:: generate_key ( ) )
668+ . body ( ( ) ) ?;
640669
641- Ok ( builder . body ( ( ) ) ? )
670+ Ok ( ( request , host ) )
642671}
0 commit comments