@@ -6,7 +6,11 @@ mod helpers;
66use helpers:: {
77 EnvVarGuard , build_ca, build_client_cert, build_server_cert, install_rustls_provider,
88} ;
9- use openshell_cli:: tls:: { TlsOptions , grpc_client} ;
9+ use openshell_bootstrap:: { get_gateway_metadata, load_active_gateway} ;
10+ use openshell_cli:: {
11+ run,
12+ tls:: { TlsOptions , grpc_client} ,
13+ } ;
1014use openshell_core:: proto:: {
1115 CreateProviderRequest , CreateSshSessionRequest , CreateSshSessionResponse ,
1216 DeleteProviderRequest , DeleteProviderResponse , ExecSandboxEvent , ExecSandboxInput ,
@@ -494,6 +498,167 @@ async fn run_server(
494498 addr
495499}
496500
501+ fn write_gateway_mtls_bundle (
502+ config_dir : & std:: path:: Path ,
503+ gateway_name : & str ,
504+ ca_cert : & str ,
505+ client_cert : & str ,
506+ client_key : & str ,
507+ ) {
508+ let mtls = config_dir
509+ . join ( "openshell" )
510+ . join ( "gateways" )
511+ . join ( gateway_name)
512+ . join ( "mtls" ) ;
513+ std:: fs:: create_dir_all ( & mtls) . unwrap ( ) ;
514+ std:: fs:: write ( mtls. join ( "ca.crt" ) , ca_cert) . unwrap ( ) ;
515+ std:: fs:: write ( mtls. join ( "tls.crt" ) , client_cert) . unwrap ( ) ;
516+ std:: fs:: write ( mtls. join ( "tls.key" ) , client_key) . unwrap ( ) ;
517+ }
518+
519+ fn isolated_gateway_add_env (
520+ config_dir : & std:: path:: Path ,
521+ state_dir : & std:: path:: Path ,
522+ ) -> EnvVarGuard {
523+ let xdg_config = config_dir. to_string_lossy ( ) . into_owned ( ) ;
524+ let xdg_state = state_dir. to_string_lossy ( ) . into_owned ( ) ;
525+ let local_tls_dir = state_dir. join ( "no-package-managed-tls" ) ;
526+ let local_tls = local_tls_dir. to_string_lossy ( ) . into_owned ( ) ;
527+
528+ EnvVarGuard :: set ( & [
529+ ( "XDG_CONFIG_HOME" , xdg_config. as_str ( ) ) ,
530+ ( "XDG_STATE_HOME" , xdg_state. as_str ( ) ) ,
531+ ( "HOME" , xdg_state. as_str ( ) ) ,
532+ ( "OPENSHELL_LOCAL_TLS_DIR" , local_tls. as_str ( ) ) ,
533+ ( "OPENSHELL_GATEWAY" , "unused-by-named-gateway-add" ) ,
534+ ] )
535+ }
536+
537+ #[ tokio:: test]
538+ async fn gateway_add_mtls_loopback_uses_explicit_gateway_name ( ) {
539+ install_rustls_provider ( ) ;
540+
541+ let ( ca, ca_key) = build_ca ( ) ;
542+ let ( server_cert, server_key) = build_server_cert ( & ca, & ca_key) ;
543+ let ( client_cert, client_key) = build_client_cert ( & ca, & ca_key) ;
544+ let ca_cert = ca. pem ( ) ;
545+ let addr = run_server ( server_cert, server_key, ca_cert. clone ( ) ) . await ;
546+
547+ let config_dir = tempdir ( ) . unwrap ( ) ;
548+ let state_dir = tempdir ( ) . unwrap ( ) ;
549+ write_gateway_mtls_bundle (
550+ config_dir. path ( ) ,
551+ "k8s" ,
552+ & ca_cert,
553+ & client_cert,
554+ & client_key,
555+ ) ;
556+ let _env = isolated_gateway_add_env ( config_dir. path ( ) , state_dir. path ( ) ) ;
557+
558+ let endpoint = format ! ( "https://localhost:{}" , addr. port( ) ) ;
559+ run:: gateway_add (
560+ & endpoint,
561+ Some ( "k8s" ) ,
562+ None ,
563+ true ,
564+ None ,
565+ "openshell-cli" ,
566+ None ,
567+ None ,
568+ false ,
569+ )
570+ . await
571+ . unwrap ( ) ;
572+
573+ let metadata = get_gateway_metadata ( "k8s" ) . unwrap ( ) ;
574+ assert_eq ! ( metadata. name, "k8s" ) ;
575+ assert_eq ! ( metadata. gateway_endpoint, endpoint) ;
576+ assert_eq ! ( metadata. auth_mode. as_deref( ) , Some ( "mtls" ) ) ;
577+ assert_eq ! ( load_active_gateway( ) . as_deref( ) , Some ( "k8s" ) ) ;
578+ assert ! ( get_gateway_metadata( "openshell" ) . is_none( ) ) ;
579+ }
580+
581+ #[ tokio:: test]
582+ async fn gateway_add_mtls_loopback_without_name_uses_openshell_default ( ) {
583+ install_rustls_provider ( ) ;
584+
585+ let ( ca, ca_key) = build_ca ( ) ;
586+ let ( server_cert, server_key) = build_server_cert ( & ca, & ca_key) ;
587+ let ( client_cert, client_key) = build_client_cert ( & ca, & ca_key) ;
588+ let ca_cert = ca. pem ( ) ;
589+ let addr = run_server ( server_cert, server_key, ca_cert. clone ( ) ) . await ;
590+
591+ let config_dir = tempdir ( ) . unwrap ( ) ;
592+ let state_dir = tempdir ( ) . unwrap ( ) ;
593+ write_gateway_mtls_bundle (
594+ config_dir. path ( ) ,
595+ "openshell" ,
596+ & ca_cert,
597+ & client_cert,
598+ & client_key,
599+ ) ;
600+ let _env = isolated_gateway_add_env ( config_dir. path ( ) , state_dir. path ( ) ) ;
601+
602+ let endpoint = format ! ( "https://localhost:{}" , addr. port( ) ) ;
603+ run:: gateway_add (
604+ & endpoint,
605+ None ,
606+ None ,
607+ true ,
608+ None ,
609+ "openshell-cli" ,
610+ None ,
611+ None ,
612+ false ,
613+ )
614+ . await
615+ . unwrap ( ) ;
616+
617+ let metadata = get_gateway_metadata ( "openshell" ) . unwrap ( ) ;
618+ assert_eq ! ( metadata. name, "openshell" ) ;
619+ assert_eq ! ( metadata. gateway_endpoint, endpoint) ;
620+ assert_eq ! ( metadata. auth_mode. as_deref( ) , Some ( "mtls" ) ) ;
621+ assert_eq ! ( load_active_gateway( ) . as_deref( ) , Some ( "openshell" ) ) ;
622+ }
623+
624+ #[ tokio:: test]
625+ async fn gateway_add_mtls_loopback_explicit_name_does_not_fallback_to_openshell_certs ( ) {
626+ install_rustls_provider ( ) ;
627+
628+ let ( ca, ca_key) = build_ca ( ) ;
629+ let ( client_cert, client_key) = build_client_cert ( & ca, & ca_key) ;
630+ let ca_cert = ca. pem ( ) ;
631+
632+ let config_dir = tempdir ( ) . unwrap ( ) ;
633+ let state_dir = tempdir ( ) . unwrap ( ) ;
634+ write_gateway_mtls_bundle (
635+ config_dir. path ( ) ,
636+ "openshell" ,
637+ & ca_cert,
638+ & client_cert,
639+ & client_key,
640+ ) ;
641+ let _env = isolated_gateway_add_env ( config_dir. path ( ) , state_dir. path ( ) ) ;
642+
643+ let err = run:: gateway_add (
644+ "https://localhost:1" ,
645+ Some ( "k8s" ) ,
646+ None ,
647+ true ,
648+ None ,
649+ "openshell-cli" ,
650+ None ,
651+ None ,
652+ false ,
653+ )
654+ . await
655+ . expect_err ( "explicit name should require matching named mTLS material" ) ;
656+
657+ assert ! ( err. to_string( ) . contains( "gateway 'k8s'" ) ) ;
658+ assert ! ( get_gateway_metadata( "k8s" ) . is_none( ) ) ;
659+ assert ! ( load_active_gateway( ) . is_none( ) ) ;
660+ }
661+
497662#[ tokio:: test]
498663async fn cli_connects_with_client_cert ( ) {
499664 install_rustls_provider ( ) ;
0 commit comments