@@ -415,6 +415,20 @@ func TestFailover_Connect(t *testing.T) {
415415 _ , _ , err := v .client .ConfigEntries ().Set (intention , & api.WriteOptions {Partition : v .partition })
416416 require .NoError (t , err )
417417 }
418+
419+ // Validate that the generated mesh-gateway ACL policy grants the
420+ // cross-partition service/node read permissions required for
421+ // admin-partition Sameness Group failover targets. Without these
422+ // permissions (nested under partition_prefix), a mesh gateway knows
423+ // about a failover target in another local partition but cannot form
424+ // the Envoy cluster for it, so failover silently fails.
425+ // See https://github.com/hashicorp/consul-k8s/pull/5423.
426+ for _ , v := range testClusters {
427+ if v .hasServer {
428+ logger .Logf (t , "verifying mesh-gateway ACL policy on %s" , v .name )
429+ v .verifyMeshGatewayACLPolicy (t )
430+ }
431+ }
418432 }
419433
420434 logger .Log (t , "creating exported services" )
@@ -854,6 +868,59 @@ func (c *cluster) getCatalogService(t *testing.T, svc, ns, partition string) *ap
854868 return resp [0 ]
855869}
856870
871+ // verifyMeshGatewayACLPolicy asserts that the mesh-gateway ACL policy generated by
872+ // server-acl-init grants cross-partition service and node read permissions nested
873+ // under a partition_prefix block. When admin partitions are enabled, these reads
874+ // are required so a mesh gateway can build the Envoy cluster for a Sameness Group
875+ // failover target that lives in another local partition. Prior to the fix in
876+ // https://github.com/hashicorp/consul-k8s/pull/5423 the reads were emitted outside
877+ // of partition_prefix (scoped to the gateway's own partition only), which caused
878+ // "Cluster not found" failures during admin-partition failover.
879+ func (c * cluster ) verifyMeshGatewayACLPolicy (t * testing.T ) {
880+ policies , _ , err := c .client .ACL ().PolicyList (& api.QueryOptions {})
881+ require .NoError (t , err )
882+
883+ var meshGWPolicyID string
884+ for _ , p := range policies {
885+ if strings .HasPrefix (p .Name , "mesh-gateway-policy" ) {
886+ meshGWPolicyID = p .ID
887+ break
888+ }
889+ }
890+ require .NotEmpty (t , meshGWPolicyID , "mesh-gateway-policy not found on cluster %s" , c .name )
891+
892+ policy , _ , err := c .client .ACL ().PolicyRead (meshGWPolicyID , & api.QueryOptions {})
893+ require .NoError (t , err )
894+ rules := policy .Rules
895+ logger .Logf (t , "mesh-gateway policy rules on %s:\n %s" , c .name , rules )
896+
897+ // The partition_prefix block must exist and must contain the service/node
898+ // read grants. We assert ordering (reads appear after partition_prefix and
899+ // before the namespace "default" mesh-gateway write block) to ensure the
900+ // reads are nested inside partition_prefix rather than scoped to the local
901+ // partition only.
902+ partitionIdx := strings .Index (rules , `partition_prefix "" {` )
903+ require .NotEqual (t , - 1 , partitionIdx ,
904+ "mesh-gateway policy on %s is missing the partition_prefix block:\n %s" , c .name , rules )
905+
906+ serviceIdx := strings .Index (rules , `service_prefix "" {` )
907+ nodeIdx := strings .Index (rules , `node_prefix "" {` )
908+ require .NotEqual (t , - 1 , serviceIdx , "mesh-gateway policy on %s is missing service_prefix read:\n %s" , c .name , rules )
909+ require .NotEqual (t , - 1 , nodeIdx , "mesh-gateway policy on %s is missing node_prefix read:\n %s" , c .name , rules )
910+
911+ require .Greater (t , serviceIdx , partitionIdx ,
912+ "service_prefix read must be nested under partition_prefix in mesh-gateway policy on %s:\n %s" , c .name , rules )
913+ require .Greater (t , nodeIdx , partitionIdx ,
914+ "node_prefix read must be nested under partition_prefix in mesh-gateway policy on %s:\n %s" , c .name , rules )
915+
916+ if nsWriteIdx := strings .Index (rules , `namespace "default" {` ); nsWriteIdx != - 1 {
917+ require .Less (t , serviceIdx , nsWriteIdx ,
918+ "service_prefix read must be within the partition_prefix block (before the namespace \" default\" block) in mesh-gateway policy on %s:\n %s" , c .name , rules )
919+ require .Less (t , nodeIdx , nsWriteIdx ,
920+ "node_prefix read must be within the partition_prefix block (before the namespace \" default\" block) in mesh-gateway policy on %s:\n %s" , c .name , rules )
921+ }
922+ }
923+
857924type clusters map [string ]* cluster
858925
859926func (c clusters ) resetScale (t * testing.T ) {
0 commit comments