Skip to content

Commit 693e6b5

Browse files
Backport of fix: update mesh gateway rules for peering and partition handling into release/1.9.x (#5447)
no-op commit due to failed cherry-picking Co-authored-by: temp <temp@hashicorp.com>
1 parent 844c79f commit 693e6b5

4 files changed

Lines changed: 240 additions & 7 deletions

File tree

.changelog/5423.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
```release-note:bug
2+
control-plane: fix mesh-gateway ACL policy generation for Admin Partition failover so Sameness Group clusters can form across local partitions.
3+
```
4+

acceptance/tests/sameness/sameness_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
857924
type clusters map[string]*cluster
858925

859926
func (c clusters) resetScale(t *testing.T) {

control-plane/subcommand/server-acl-init/rules.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,38 @@ func (c *Command) meshGatewayRules() (string, error) {
152152
// namespaces, it needs access to all namespaces. For peering, it requires the ability to list all peers which in
153153
// enterprise requires peering:read on all partitions or in OSS requires a top level peering:read. Since we cannot
154154
// determine whether we are using an enterprise or OSS consul image based on whether peering is enabled, we include
155-
// both permissions here.
155+
// both permissions here. When admin partitions are enabled, the default partition's mesh gateway additionally needs
156+
// service and node read permissions under partition_prefix so it can form clusters for failover targets in other
157+
// local partitions (e.g. Sameness Group failover). The partition_prefix block is only emitted for the default
158+
// partition because partition rules are not valid in a policy scoped to a non-default admin partition; non-default
159+
// partitions fall back to local namespace-scoped reads.
156160
meshGatewayRulesTpl := `mesh = "write"
157161
{{- if .EnablePeering }}
158162
peering = "read"
163+
{{- end }}
159164
{{- if eq .PartitionName "default" }}
160165
partition_prefix "" {
166+
{{- if .EnablePeering }}
161167
peering = "read"
162-
}
163168
{{- end }}
169+
{{- if .EnableNamespaces }}
170+
namespace_prefix "" {
171+
node_prefix "" {
172+
policy = "read"
173+
}
174+
service_prefix "" {
175+
policy = "read"
176+
}
177+
}
178+
{{- else }}
179+
node_prefix "" {
180+
policy = "read"
181+
}
182+
service_prefix "" {
183+
policy = "read"
184+
}
185+
{{- end }}
186+
}
164187
{{- end }}
165188
{{- if .EnableNamespaces }}
166189
namespace "default" {
@@ -170,6 +193,9 @@ namespace "default" {
170193
}
171194
{{- if .EnableNamespaces }}
172195
}
196+
{{- end }}
197+
{{- if ne .PartitionName "default" }}
198+
{{- if .EnableNamespaces }}
173199
namespace_prefix "" {
174200
{{- end }}
175201
node_prefix "" {
@@ -181,6 +207,7 @@ namespace_prefix "" {
181207
{{- if .EnableNamespaces }}
182208
}
183209
{{- end }}
210+
{{- end }}
184211
`
185212

186213
return c.renderRules(meshGatewayRulesTpl)

control-plane/subcommand/server-acl-init/rules_test.go

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,23 @@ peering = "read"
204204
peering = "read"
205205
partition_prefix "" {
206206
peering = "read"
207+
node_prefix "" {
208+
policy = "read"
209+
}
210+
service_prefix "" {
211+
policy = "read"
212+
}
207213
}
214+
service "mesh-gateway" {
215+
policy = "write"
216+
}`,
217+
},
218+
{
219+
Name: "Peering is enabled with partition explicitly specified as non-default (ent non-default case)",
220+
EnablePeering: true,
221+
PartitionName: "non-default",
222+
Expected: `mesh = "write"
223+
peering = "read"
208224
service "mesh-gateway" {
209225
policy = "write"
210226
}
@@ -216,27 +232,128 @@ partition_prefix "" {
216232
}`,
217233
},
218234
{
219-
Name: "Peering is enabled with partition explicitly specified as non-default (ent non-default case)",
220-
EnablePeering: true,
221-
PartitionName: "non-default",
235+
Name: "Peering and namespaces are enabled",
236+
EnablePeering: true,
237+
EnableNamespaces: true,
222238
Expected: `mesh = "write"
223239
peering = "read"
240+
namespace "default" {
224241
service "mesh-gateway" {
225242
policy = "write"
226243
}
244+
}
245+
namespace_prefix "" {
227246
node_prefix "" {
228247
policy = "read"
229248
}
230249
service_prefix "" {
231250
policy = "read"
232-
}`,
251+
}
252+
}`,
233253
},
234254
{
235-
Name: "Peering and namespaces are enabled",
255+
Name: "Peering, namespaces, and default partition are enabled",
236256
EnablePeering: true,
237257
EnableNamespaces: true,
258+
PartitionName: "default",
238259
Expected: `mesh = "write"
239260
peering = "read"
261+
partition_prefix "" {
262+
peering = "read"
263+
namespace_prefix "" {
264+
node_prefix "" {
265+
policy = "read"
266+
}
267+
service_prefix "" {
268+
policy = "read"
269+
}
270+
}
271+
}
272+
namespace "default" {
273+
service "mesh-gateway" {
274+
policy = "write"
275+
}
276+
}`,
277+
},
278+
{
279+
Name: "Peering, namespaces, and non-default partition are enabled",
280+
EnablePeering: true,
281+
EnableNamespaces: true,
282+
PartitionName: "non-default",
283+
Expected: `mesh = "write"
284+
peering = "read"
285+
namespace "default" {
286+
service "mesh-gateway" {
287+
policy = "write"
288+
}
289+
}
290+
namespace_prefix "" {
291+
node_prefix "" {
292+
policy = "read"
293+
}
294+
service_prefix "" {
295+
policy = "read"
296+
}
297+
}`,
298+
},
299+
{
300+
// Sameness Group failover across local admin partitions only
301+
// (no cluster peering). The partition_prefix read grants must
302+
// still be present on the default partition's gateway so it can
303+
// form failover clusters for services in sibling partitions.
304+
Name: "Namespaces and default partition are enabled, peering disabled",
305+
EnablePeering: false,
306+
EnableNamespaces: true,
307+
PartitionName: "default",
308+
Expected: `mesh = "write"
309+
partition_prefix "" {
310+
namespace_prefix "" {
311+
node_prefix "" {
312+
policy = "read"
313+
}
314+
service_prefix "" {
315+
policy = "read"
316+
}
317+
}
318+
}
319+
namespace "default" {
320+
service "mesh-gateway" {
321+
policy = "write"
322+
}
323+
}`,
324+
},
325+
{
326+
// Partitions enabled with neither peering nor namespaces. The
327+
// chart forbids partitions without namespaces, but the template
328+
// must still render valid HCL via the else branch.
329+
Name: "Default partition is enabled, peering and namespaces disabled",
330+
EnablePeering: false,
331+
EnableNamespaces: false,
332+
PartitionName: "default",
333+
Expected: `mesh = "write"
334+
partition_prefix "" {
335+
node_prefix "" {
336+
policy = "read"
337+
}
338+
service_prefix "" {
339+
policy = "read"
340+
}
341+
}
342+
service "mesh-gateway" {
343+
policy = "write"
344+
}`,
345+
},
346+
{
347+
// Regression guard: a non-default admin partition must NOT emit a
348+
// partition_prefix block, because partition rules are invalid in a
349+
// policy scoped to a non-default partition. server-acl-init would
350+
// otherwise fail to create the mesh-gateway policy, leaving the
351+
// gateway pod unready (see PR #5423 follow-up).
352+
Name: "Namespaces and non-default partition are enabled, peering disabled",
353+
EnablePeering: false,
354+
EnableNamespaces: true,
355+
PartitionName: "non-default",
356+
Expected: `mesh = "write"
240357
namespace "default" {
241358
service "mesh-gateway" {
242359
policy = "write"
@@ -251,6 +368,24 @@ namespace_prefix "" {
251368
}
252369
}`,
253370
},
371+
{
372+
// Regression guard: non-default partition without namespaces also
373+
// must not emit a partition_prefix block.
374+
Name: "Non-default partition is enabled, peering and namespaces disabled",
375+
EnablePeering: false,
376+
EnableNamespaces: false,
377+
PartitionName: "non-default",
378+
Expected: `mesh = "write"
379+
service "mesh-gateway" {
380+
policy = "write"
381+
}
382+
node_prefix "" {
383+
policy = "read"
384+
}
385+
service_prefix "" {
386+
policy = "read"
387+
}`,
388+
},
254389
}
255390

256391
for _, tt := range cases {

0 commit comments

Comments
 (0)