@@ -468,8 +468,9 @@ impl AdminRpc for AdminRpcHandler {
468468 let kv_store = self . state . kv_store ( ) ;
469469 let cert_resolver = & self . state . cert_resolver ;
470470
471+ let domain = normalize_zt_domain ( & request. domain ) ?;
471472 let config = kv_store
472- . get_zt_domain_config ( & request . domain )
473+ . get_zt_domain_config ( & domain)
473474 . context ( "ZT-Domain config not found" ) ?;
474475
475476 Ok ( zt_domain_to_proto ( config, kv_store, cert_resolver) )
@@ -479,13 +480,14 @@ impl AdminRpc for AdminRpcHandler {
479480 let kv_store = self . state . kv_store ( ) ;
480481 let cert_resolver = & self . state . cert_resolver ;
481482
482- // Check if domain already exists
483- if kv_store. get_zt_domain_config ( & request. domain ) . is_some ( ) {
484- bail ! ( "ZT-Domain config already exists: {}" , request. domain) ;
485- }
486-
487483 let config = proto_to_zt_domain_config ( & request, kv_store) ?;
488484
485+ // Uniqueness is checked after normalization so wildcard, case, and a
486+ // trailing root dot cannot silently overwrite the same DNS name.
487+ if kv_store. get_zt_domain_config ( & config. domain ) . is_some ( ) {
488+ bail ! ( "ZT-Domain config already exists: {}" , config. domain) ;
489+ }
490+
489491 kv_store. save_zt_domain_config ( & config) ?;
490492 info ! ( "Added ZT-Domain config: {}" , config. domain) ;
491493
@@ -496,13 +498,13 @@ impl AdminRpc for AdminRpcHandler {
496498 let kv_store = self . state . kv_store ( ) ;
497499 let cert_resolver = & self . state . cert_resolver ;
498500
499- // Check if config exists
501+ let config = proto_to_zt_domain_config ( & request, kv_store) ?;
502+
503+ // Check the normalized key rather than the caller's presentation.
500504 kv_store
501- . get_zt_domain_config ( & request . domain )
505+ . get_zt_domain_config ( & config . domain )
502506 . context ( "ZT-Domain config not found" ) ?;
503507
504- let config = proto_to_zt_domain_config ( & request, kv_store) ?;
505-
506508 kv_store. save_zt_domain_config ( & config) ?;
507509 info ! ( "Updated ZT-Domain config: {}" , config. domain) ;
508510
@@ -512,14 +514,14 @@ impl AdminRpc for AdminRpcHandler {
512514 async fn delete_zt_domain ( self , request : DeleteZtDomainRequest ) -> Result < ( ) > {
513515 let kv_store = self . state . kv_store ( ) ;
514516
515- // Check if config exists
517+ let domain = normalize_zt_domain ( & request . domain ) ? ;
516518 kv_store
517- . get_zt_domain_config ( & request . domain )
519+ . get_zt_domain_config ( & domain)
518520 . context ( "ZT-Domain config not found" ) ?;
519521
520522 // Delete config (cert data, acme, attestations are kept for historical purposes)
521- kv_store. delete_zt_domain_config ( & request . domain ) ?;
522- info ! ( "Deleted ZT-Domain config: {}" , request . domain ) ;
523+ kv_store. delete_zt_domain_config ( & domain) ?;
524+ info ! ( "Deleted ZT-Domain config: {domain}" ) ;
523525 Ok ( ( ) )
524526 }
525527
@@ -792,6 +794,16 @@ fn redact_token(token: &str) -> String {
792794 }
793795}
794796
797+ fn normalize_zt_domain ( domain : & str ) -> Result < String > {
798+ let domain = domain. trim ( ) . trim_end_matches ( '.' ) ;
799+ let domain = domain
800+ . strip_prefix ( "*." )
801+ . unwrap_or ( domain)
802+ . to_ascii_lowercase ( ) ;
803+ validate_zt_domain ( & domain) ?;
804+ Ok ( domain)
805+ }
806+
795807fn validate_zt_domain ( domain : & str ) -> Result < ( ) > {
796808 if domain. is_empty ( ) || domain. len ( ) > 253 || !domain. is_ascii ( ) {
797809 bail ! ( "domain must be a non-empty ASCII DNS name of at most 253 bytes" ) ;
@@ -830,13 +842,7 @@ fn proto_to_zt_domain_config(
830842 . context ( "specified dns credential not found" ) ?;
831843 }
832844
833- // Strip wildcard prefix if user entered it
834- let domain = proto
835- . domain
836- . strip_prefix ( "*." )
837- . unwrap_or ( & proto. domain )
838- . to_string ( ) ;
839- validate_zt_domain ( & domain) ?;
845+ let domain = normalize_zt_domain ( & proto. domain ) ?;
840846 if proto. port == 0 {
841847 bail ! ( "port must be between 1 and 65535" ) ;
842848 }
0 commit comments