@@ -473,8 +473,9 @@ impl AdminRpc for AdminRpcHandler {
473473 let kv_store = self . state . kv_store ( ) ;
474474 let cert_resolver = & self . state . cert_resolver ;
475475
476+ let domain = normalize_zt_domain ( & request. domain ) ?;
476477 let config = kv_store
477- . get_zt_domain_config ( & request . domain )
478+ . get_zt_domain_config ( & domain)
478479 . context ( "ZT-Domain config not found" ) ?;
479480
480481 Ok ( zt_domain_to_proto ( config, kv_store, cert_resolver) )
@@ -484,13 +485,14 @@ impl AdminRpc for AdminRpcHandler {
484485 let kv_store = self . state . kv_store ( ) ;
485486 let cert_resolver = & self . state . cert_resolver ;
486487
487- // Check if domain already exists
488- if kv_store. get_zt_domain_config ( & request. domain ) . is_some ( ) {
489- bail ! ( "ZT-Domain config already exists: {}" , request. domain) ;
490- }
491-
492488 let config = proto_to_zt_domain_config ( & request, kv_store) ?;
493489
490+ // Uniqueness is checked after normalization so wildcard, case, and a
491+ // trailing root dot cannot silently overwrite the same DNS name.
492+ if kv_store. get_zt_domain_config ( & config. domain ) . is_some ( ) {
493+ bail ! ( "ZT-Domain config already exists: {}" , config. domain) ;
494+ }
495+
494496 kv_store. save_zt_domain_config ( & config) ?;
495497 info ! ( "Added ZT-Domain config: {}" , config. domain) ;
496498
@@ -501,13 +503,13 @@ impl AdminRpc for AdminRpcHandler {
501503 let kv_store = self . state . kv_store ( ) ;
502504 let cert_resolver = & self . state . cert_resolver ;
503505
504- // Check if config exists
506+ let config = proto_to_zt_domain_config ( & request, kv_store) ?;
507+
508+ // Check the normalized key rather than the caller's presentation.
505509 kv_store
506- . get_zt_domain_config ( & request . domain )
510+ . get_zt_domain_config ( & config . domain )
507511 . context ( "ZT-Domain config not found" ) ?;
508512
509- let config = proto_to_zt_domain_config ( & request, kv_store) ?;
510-
511513 kv_store. save_zt_domain_config ( & config) ?;
512514 info ! ( "Updated ZT-Domain config: {}" , config. domain) ;
513515
@@ -517,14 +519,14 @@ impl AdminRpc for AdminRpcHandler {
517519 async fn delete_zt_domain ( self , request : DeleteZtDomainRequest ) -> Result < ( ) > {
518520 let kv_store = self . state . kv_store ( ) ;
519521
520- // Check if config exists
522+ let domain = normalize_zt_domain ( & request . domain ) ? ;
521523 kv_store
522- . get_zt_domain_config ( & request . domain )
524+ . get_zt_domain_config ( & domain)
523525 . context ( "ZT-Domain config not found" ) ?;
524526
525527 // Delete config (cert data, acme, attestations are kept for historical purposes)
526- kv_store. delete_zt_domain_config ( & request . domain ) ?;
527- info ! ( "Deleted ZT-Domain config: {}" , request . domain ) ;
528+ kv_store. delete_zt_domain_config ( & domain) ?;
529+ info ! ( "Deleted ZT-Domain config: {domain}" ) ;
528530 Ok ( ( ) )
529531 }
530532
@@ -797,6 +799,16 @@ fn redact_token(token: &str) -> String {
797799 }
798800}
799801
802+ fn normalize_zt_domain ( domain : & str ) -> Result < String > {
803+ let domain = domain. trim ( ) . trim_end_matches ( '.' ) ;
804+ let domain = domain
805+ . strip_prefix ( "*." )
806+ . unwrap_or ( domain)
807+ . to_ascii_lowercase ( ) ;
808+ validate_zt_domain ( & domain) ?;
809+ Ok ( domain)
810+ }
811+
800812fn validate_zt_domain ( domain : & str ) -> Result < ( ) > {
801813 if domain. is_empty ( ) || domain. len ( ) > 253 || !domain. is_ascii ( ) {
802814 bail ! ( "domain must be a non-empty ASCII DNS name of at most 253 bytes" ) ;
@@ -835,13 +847,7 @@ fn proto_to_zt_domain_config(
835847 . context ( "specified dns credential not found" ) ?;
836848 }
837849
838- // Strip wildcard prefix if user entered it
839- let domain = proto
840- . domain
841- . strip_prefix ( "*." )
842- . unwrap_or ( & proto. domain )
843- . to_string ( ) ;
844- validate_zt_domain ( & domain) ?;
850+ let domain = normalize_zt_domain ( & proto. domain ) ?;
845851 if proto. port == 0 {
846852 bail ! ( "port must be between 1 and 65535" ) ;
847853 }
0 commit comments