diff --git a/pkg/i2gw/intermediate/provider_gce.go b/pkg/i2gw/intermediate/provider_gce.go index ebd9b512..d37aec68 100644 --- a/pkg/i2gw/intermediate/provider_gce.go +++ b/pkg/i2gw/intermediate/provider_gce.go @@ -22,11 +22,15 @@ type GceGatewayIR struct { type GceHTTPRouteIR struct{} type GceServiceIR struct { SessionAffinity *SessionAffinityConfig + SecurityPolicy *SecurityPolicyConfig } type SessionAffinityConfig struct { AffinityType string CookieTTLSec *int64 } +type SecurityPolicyConfig struct { + Name string +} func mergeGceGatewayIR(current, existing *GceGatewayIR) *GceGatewayIR { // If either GceGatewayIR is nil, return the other one as the merged result. diff --git a/pkg/i2gw/providers/gce/extensions/input_extensions.go b/pkg/i2gw/providers/gce/extensions/input_extensions.go index 3f154a23..762bf7c1 100644 --- a/pkg/i2gw/providers/gce/extensions/input_extensions.go +++ b/pkg/i2gw/providers/gce/extensions/input_extensions.go @@ -46,3 +46,9 @@ func BuildIRSessionAffinityConfig(beConfig *backendconfigv1.BackendConfig) *inte CookieTTLSec: beConfig.Spec.SessionAffinity.AffinityCookieTtlSec, } } + +func BuildIRSecurityPolicyConfig(beConfig *backendconfigv1.BackendConfig) *intermediate.SecurityPolicyConfig { + return &intermediate.SecurityPolicyConfig{ + Name: beConfig.Spec.SecurityPolicy.Name, + } +} diff --git a/pkg/i2gw/providers/gce/extensions/output_extensions.go b/pkg/i2gw/providers/gce/extensions/output_extensions.go index aa946d91..19b4ca2e 100644 --- a/pkg/i2gw/providers/gce/extensions/output_extensions.go +++ b/pkg/i2gw/providers/gce/extensions/output_extensions.go @@ -31,3 +31,8 @@ func BuildBackendPolicySessionAffinityConfig(serviceIR intermediate.ProviderSpec } return &saConfig } + +func BuildBackendPolicySecurityPolicyConfig(serviceIR intermediate.ProviderSpecificServiceIR) *string { + securityPolicy := serviceIR.Gce.SecurityPolicy.Name + return &securityPolicy +} diff --git a/pkg/i2gw/providers/gce/gateway_converter_test.go b/pkg/i2gw/providers/gce/gateway_converter_test.go index 7b4de538..b0f0aa4f 100644 --- a/pkg/i2gw/providers/gce/gateway_converter_test.go +++ b/pkg/i2gw/providers/gce/gateway_converter_test.go @@ -46,6 +46,7 @@ func Test_irToGateway(t *testing.T) { saTypeClientIP := "CLIENT_IP" testCookieTTLSec := int64(10) saTypeCookie := "GENERATED_COOKIE" + testSecurityPolicy := "test-security-policy" testGateway := gatewayv1.Gateway{ ObjectMeta: metav1.ObjectMeta{Name: testGatewayName, Namespace: testNamespace}, @@ -143,6 +144,28 @@ func Test_irToGateway(t *testing.T) { if err != nil { t.Errorf("Failed to generate unstructured GCP Backend Policy with ClientIP-based session affinity feature: %v", err) } + + testSpGCPBackendPolicy := gkegatewayv1.GCPBackendPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: testSaBackendPolicyName, + }, + Spec: gkegatewayv1.GCPBackendPolicySpec{ + Default: &gkegatewayv1.GCPBackendPolicyConfig{ + SecurityPolicy: &testSecurityPolicy, + }, + TargetRef: v1alpha2.NamespacedPolicyTargetReference{ + Group: "", + Kind: "Service", + Name: gatewayv1.ObjectName(testServiceName), + }, + }, + } + testSpGCPBackendPolicy.SetGroupVersionKind(GCPBackendPolicyGVK) + testSpGCPBackendPolicyUnstructured, err := i2gw.CastToUnstructured(&testSpGCPBackendPolicy) + if err != nil { + t.Errorf("Failed to generate unstructured GCP Backend Policy with Security Policy feature: %v", err) + } testCases := []struct { name string ir intermediate.IR @@ -222,6 +245,42 @@ func Test_irToGateway(t *testing.T) { }, expectedErrors: field.ErrorList{}, }, + { + name: "ingress with a Backend Config specifying Security Policy", + ir: intermediate.IR{ + Gateways: map[types.NamespacedName]intermediate.GatewayContext{ + {Namespace: testNamespace, Name: testGatewayName}: { + Gateway: testGateway, + }, + }, + HTTPRoutes: map[types.NamespacedName]intermediate.HTTPRouteContext{ + {Namespace: testNamespace, Name: testHTTPRouteName}: { + HTTPRoute: testHTTPRoute, + }, + }, + Services: map[types.NamespacedName]intermediate.ProviderSpecificServiceIR{ + {Namespace: testNamespace, Name: testServiceName}: { + Gce: &intermediate.GceServiceIR{ + SecurityPolicy: &intermediate.SecurityPolicyConfig{ + Name: testSecurityPolicy, + }, + }, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: testNamespace, Name: testGatewayName}: testGateway, + }, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{ + {Namespace: testNamespace, Name: testHTTPRouteName}: testHTTPRoute, + }, + GatewayExtensions: []unstructured.Unstructured{ + *testSpGCPBackendPolicyUnstructured, + }, + }, + expectedErrors: field.ErrorList{}, + }, } for _, tc := range testCases { diff --git a/pkg/i2gw/providers/gce/gce_extensions.go b/pkg/i2gw/providers/gce/gce_extensions.go index f480d7d5..8cb0a9ae 100644 --- a/pkg/i2gw/providers/gce/gce_extensions.go +++ b/pkg/i2gw/providers/gce/gce_extensions.go @@ -148,6 +148,9 @@ func beConfigToGceServiceIR(beConfig *backendconfigv1.BackendConfig) intermediat if beConfig.Spec.SessionAffinity != nil { gceServiceIR.SessionAffinity = extensions.BuildIRSessionAffinityConfig(beConfig) } + if beConfig.Spec.SecurityPolicy != nil { + gceServiceIR.SecurityPolicy = extensions.BuildIRSecurityPolicyConfig(beConfig) + } return gceServiceIR } @@ -190,6 +193,9 @@ func addBackendPolicyIfConfigured(serviceNamespacedName types.NamespacedName, se if serviceIR.Gce.SessionAffinity != nil { backendPolicy.Spec.Default.SessionAffinity = extensions.BuildBackendPolicySessionAffinityConfig(serviceIR) } + if serviceIR.Gce.SecurityPolicy != nil { + backendPolicy.Spec.Default.SecurityPolicy = extensions.BuildBackendPolicySecurityPolicyConfig(serviceIR) + } return &backendPolicy } diff --git a/pkg/i2gw/providers/gce/ir_converter_test.go b/pkg/i2gw/providers/gce/ir_converter_test.go index f094991b..6b4d4a84 100644 --- a/pkg/i2gw/providers/gce/ir_converter_test.go +++ b/pkg/i2gw/providers/gce/ir_converter_test.go @@ -54,6 +54,7 @@ func Test_convertToIR(t *testing.T) { saTypeClientIP := "CLIENT_IP" testCookieTTLSec := int64(10) saTypeCookie := "GENERATED_COOKIE" + testSecurityPolicy := "test-security-policy" testCases := []struct { name string @@ -889,6 +890,128 @@ func Test_convertToIR(t *testing.T) { }, expectedErrors: field.ErrorList{}, }, + { + name: "ingress with a Backend Config specifying Security Policy", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: extIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: extIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/", + PathType: &iPrefix, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + services: map[types.NamespacedName]*apiv1.Service{ + {Namespace: testNamespace, Name: testServiceName}: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: testServiceName, + Annotations: map[string]string{ + backendConfigKey: `{"default":"test-backendconfig"}`, + }, + }, + }, + }, + backendConfigs: map[types.NamespacedName]*backendconfigv1.BackendConfig{ + {Namespace: testNamespace, Name: testBackendConfigName}: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: testBackendConfigName, + }, + Spec: backendconfigv1.BackendConfigSpec{ + SecurityPolicy: &backendconfigv1.SecurityPolicyConfig{ + Name: testSecurityPolicy, + }, + }, + }, + }, + expectedIR: intermediate.IR{ + Gateways: map[types.NamespacedName]intermediate.GatewayContext{ + {Namespace: testNamespace, Name: gceIngressClass}: { + Gateway: gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{Name: gceIngressClass, Namespace: testNamespace}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gceL7GlobalExternalManagedGatewayClass, + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname(testHost)), + }}, + }, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]intermediate.HTTPRouteContext{ + {Namespace: testNamespace, Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName)}: { + HTTPRoute: gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName), Namespace: testNamespace}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: gceIngressClass, + }}, + }, + Hostnames: []gatewayv1.Hostname{gatewayv1.Hostname(testHost)}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &gPathPrefix, + Value: ptrTo("/"), + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(testServiceName), + Port: ptrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Services: map[types.NamespacedName]intermediate.ProviderSpecificServiceIR{ + {Namespace: testNamespace, Name: testServiceName}: { + Gce: &intermediate.GceServiceIR{ + SecurityPolicy: &intermediate.SecurityPolicyConfig{ + Name: testSecurityPolicy, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, } for _, tc := range testCases {