diff --git a/api/gateway/v1alpha1/peering_types.go b/api/gateway/v1alpha1/peering_types.go index 53fb0f27..650430b9 100644 --- a/api/gateway/v1alpha1/peering_types.go +++ b/api/gateway/v1alpha1/peering_types.go @@ -180,6 +180,11 @@ func (p *Peering) Validate(ctx context.Context, kube kclient.Reader) error { if len(vpcs) != 2 { return fmt.Errorf("peering must have exactly 2 VPCs, got %d", len(vpcs)) //nolint:err113 } + // track the NAT on each side of the peering and disallow unsupported configurations + vpcNAT := make(map[string]struct { + Stateful bool + Stateless bool + }, 2) for name, vpc := range p.Spec.Peering { if vpc == nil { continue @@ -239,12 +244,21 @@ func (p *Peering) Validate(ctx context.Context, kube kclient.Reader) error { nonNils := 0 if expose.NAT.Static != nil { nonNils++ + vpcEntry := vpcNAT[name] + vpcEntry.Stateless = true + vpcNAT[name] = vpcEntry } if expose.NAT.Masquerade != nil { nonNils++ + vpcEntry := vpcNAT[name] + vpcEntry.Stateful = true + vpcNAT[name] = vpcEntry } if expose.NAT.PortForward != nil { nonNils++ + vpcEntry := vpcNAT[name] + vpcEntry.Stateful = true + vpcNAT[name] = vpcEntry } if nonNils != 1 { @@ -273,6 +287,12 @@ func (p *Peering) Validate(ctx context.Context, kube kclient.Reader) error { } } } + if vpcNAT[vpcs[0]].Stateful && vpcNAT[vpcs[1]].Stateful { + return fmt.Errorf("unsupported configuration, only one side of a peering can use stateful NAT (i.e. masquerade or portForward)") //nolint:err113 + } + if (vpcNAT[vpcs[0]].Stateless && vpcNAT[vpcs[1]].Stateful) || (vpcNAT[vpcs[1]].Stateless && vpcNAT[vpcs[0]].Stateful) { + return fmt.Errorf("unsupported configuration, one side of a peering using static NAT cannot peer with a side using stateful NAT") //nolint:err113 + } if kube != nil { gwGroup := &GatewayGroup{} diff --git a/api/gateway/v1alpha1/peering_types_test.go b/api/gateway/v1alpha1/peering_types_test.go index 1f734348..d9bd776f 100644 --- a/api/gateway/v1alpha1/peering_types_test.go +++ b/api/gateway/v1alpha1/peering_types_test.go @@ -188,7 +188,7 @@ func TestPeeringWithStaticNAT(t *testing.T) { assert.Equal(t, ref, peering) } -func TestPeeringWithMasqueradeNAT(t *testing.T) { +func TestPeeringWithDoubleMasqueradeNAT(t *testing.T) { common := &Peering{} common.Spec.Peering = map[string]*PeeringEntry{ "vpc1": { @@ -239,8 +239,56 @@ func TestPeeringWithMasqueradeNAT(t *testing.T) { peering := common.DeepCopy() peering.Default() - assert.NoError(t, peering.Validate(t.Context(), nil), "peering should be valid") + assert.Error(t, peering.Validate(t.Context(), nil), "masquerade on both sides should not be allowed") + assert.Equal(t, ref, peering) +} + +func TestPeeringWithMasqueradeAndStaticNAT(t *testing.T) { + common := &Peering{} + common.Spec.Peering = map[string]*PeeringEntry{ + "vpc1": { + Expose: []PeeringEntryExpose{ + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.1.0/24"}, + }, + As: []PeeringEntryAs{ + {CIDR: "192.168.1.0/24"}, + }, + NAT: &PeeringNAT{ + Static: &PeeringNATStatic{}, + }, + }, + }, + }, + "vpc2": { + Expose: []PeeringEntryExpose{ + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.2.0/24"}, + }, + As: []PeeringEntryAs{ + {CIDR: "192.168.2.0/24"}, + }, + NAT: &PeeringNAT{ + Masquerade: &PeeringNATMasquerade{ + IdleTimeout: kmetav1.Duration{Duration: time.Duration(3 * time.Minute)}, + }, + }, + }, + }, + }, + } + ref := common.DeepCopy() + ref.Labels = map[string]string{ + ListLabelVPC("vpc1"): "true", + ListLabelVPC("vpc2"): "true", + } + ref.Spec.GatewayGroup = DefaultGatewayGroup + peering := common.DeepCopy() + peering.Default() + assert.Error(t, peering.Validate(t.Context(), nil), "masquerade plus static should not be allowed") assert.Equal(t, ref, peering) } @@ -294,6 +342,123 @@ func TestPeeringWithPortForwardNAT(t *testing.T) { assert.Equal(t, ref, peering) } +func TestPeeringWithPortForwardAndMasqueradeSameSideNAT(t *testing.T) { + common := &Peering{} + common.Spec.Peering = map[string]*PeeringEntry{ + "vpc1": { + Expose: []PeeringEntryExpose{ + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.1.0/24"}, + }, + As: []PeeringEntryAs{ + {CIDR: "192.168.1.0/24"}, + }, + NAT: &PeeringNAT{ + PortForward: &PeeringNATPortForward{ + Ports: []PeeringNATPortForwardEntry{ + {Protocol: "tcp", Port: "80", As: "8080"}, + {Protocol: "udp", Port: "90-100", As: "8090-8100"}, + {Port: "88", As: "8088"}, + }, + IdleTimeout: kmetav1.Duration{Duration: DefaultPortForwardIdleTimeout}, + }, + }, + }, + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.2.0/24"}, + }, + As: []PeeringEntryAs{ + {CIDR: "192.168.2.0/24"}, + }, + NAT: &PeeringNAT{ + Masquerade: &PeeringNATMasquerade{ + IdleTimeout: kmetav1.Duration{Duration: time.Duration(3 * time.Minute)}, + }, + }, + }, + }, + }, + "vpc2": { + Expose: []PeeringEntryExpose{ + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.3.0/24"}, + }, + }, + }, + }, + } + + ref := common.DeepCopy() + ref.Labels = map[string]string{ + ListLabelVPC("vpc1"): "true", + ListLabelVPC("vpc2"): "true", + } + ref.Spec.GatewayGroup = DefaultGatewayGroup + peering := common.DeepCopy() + peering.Default() + assert.NoError(t, peering.Validate(t.Context(), nil), "peering should be valid") + assert.Equal(t, ref, peering) +} + +func TestPeeringWithPortForwardAndMasqueradeNAT(t *testing.T) { + common := &Peering{} + common.Spec.Peering = map[string]*PeeringEntry{ + "vpc1": { + Expose: []PeeringEntryExpose{ + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.1.0/24"}, + }, + As: []PeeringEntryAs{ + {CIDR: "192.168.1.0/24"}, + }, + NAT: &PeeringNAT{ + PortForward: &PeeringNATPortForward{ + Ports: []PeeringNATPortForwardEntry{ + {Protocol: "tcp", Port: "80", As: "8080"}, + {Protocol: "udp", Port: "90-100", As: "8090-8100"}, + {Port: "88", As: "8088"}, + }, + IdleTimeout: kmetav1.Duration{Duration: DefaultPortForwardIdleTimeout}, + }, + }, + }, + }, + }, + "vpc2": { + Expose: []PeeringEntryExpose{ + { + IPs: []PeeringEntryIP{ + {CIDR: "10.0.2.0/24"}, + }, + As: []PeeringEntryAs{ + {CIDR: "192.168.2.0/24"}, + }, + NAT: &PeeringNAT{ + Masquerade: &PeeringNATMasquerade{ + IdleTimeout: kmetav1.Duration{Duration: time.Duration(3 * time.Minute)}, + }, + }, + }, + }, + }, + } + + ref := common.DeepCopy() + ref.Labels = map[string]string{ + ListLabelVPC("vpc1"): "true", + ListLabelVPC("vpc2"): "true", + } + ref.Spec.GatewayGroup = DefaultGatewayGroup + peering := common.DeepCopy() + peering.Default() + assert.Error(t, peering.Validate(t.Context(), nil), "masquerade + portForward should not be allowed") + assert.Equal(t, ref, peering) +} + func TestValidateDefaultDestination(t *testing.T) { for _, tt := range []struct { name string