diff --git a/charts/kube-ovn-v2/README.md b/charts/kube-ovn-v2/README.md index 1f530077195..c4d1dcfb249 100644 --- a/charts/kube-ovn-v2/README.md +++ b/charts/kube-ovn-v2/README.md @@ -1,6 +1,6 @@ # Helm chart for Kube-OVN -![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-informational?style=flat-square) ![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-informational?style=flat-square) +![Version: 1.15.0](https://img.shields.io/badge/Version-1.15.0-informational?style=flat-square) ![Version: 1.15.0](https://img.shields.io/badge/Version-1.15.0-informational?style=flat-square) This is the v2 of the Helm Chart, replacing the first version in the long term. Make sure to adjust your old values with the new ones and pre-generate your templates with a dry-run to ensure no breaking change occurs. @@ -474,6 +474,114 @@ false +

Global parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
clusterDomainstring
+"cluster.local"
+
+
Domain used by the cluster.
fullnameOverridestring
+""
+
+
Full name override.
globalobject
+{
+  "images": {
+    "kubeovn": {
+      "repository": "kube-ovn",
+      "support_arm": true,
+      "tag": "v1.14.0",
+      "thirdparty": true,
+      "vpcRepository": "vpc-nat-gateway"
+    }
+  },
+  "registry": {
+    "address": "docker.io/kubeovn",
+    "imagePullSecrets": []
+  }
+}
+
+
Global configuration.
imageobject
+"{}"
+
+
Image configuration.
image.pullPolicystring
+"IfNotPresent"
+
+
Pull policy for all images.
masterNodeslist
+[]
+
+
Comma-separated list of IPs for each master node. If not specified, fallback to auto-identifying masters based on "masterNodesLabels"
masterNodesLabelsobject
+{
+  "kube-ovn/role": "master"
+}
+
+
Label used to auto-identify masters. Any node that has any of these labels will be considered a master node. Note: This feature uses Helm "lookup" function, which is not compatible with tools such as ArgoCD.
nameOverridestring
+""
+
+
Name override.
namespacestring
+"kube-system"
+
+
Namespace in which the CNI is deployed.

CNI configuration

@@ -537,6 +645,15 @@ false + + + + + + @@ -674,139 +791,149 @@ false - -
Whether to mount the node's tooling directory into the pod.
cni.nonPrimaryCNIbool
+false
+
+
Whether to use Kube-OVN as non-primary CNI. When set to true, Kube-OVN will not allocate/handle primary network interfaces. Interfaces are created using Network Attachment Definitions (NADs)
cni.toolingDirectory stringobject
 {
-  "CHECK_GATEWAY": true,
   "ENABLE_ANP": false,
   "ENABLE_BIND_LOCAL_IP": true,
-  "ENABLE_EXTERNAL_VPC": true,
-  "ENABLE_IC": false,
-  "ENABLE_KEEP_VM_IP": true,
-  "ENABLE_LB": true,
-  "ENABLE_LB_SVC": false,
-  "ENABLE_LIVE_MIGRATION_OPTIMIZE": true,
-  "ENABLE_NAT_GW": true,
-  "ENABLE_NP": true,
-  "ENABLE_OVN_IPSEC": false,
+  "ENABLE_DNS_NAME_RESOLVER": false,
   "ENABLE_OVN_LB_PREFER_LOCAL": false,
-  "ENABLE_TPROXY": false,
-  "HW_OFFLOAD": false,
-  "LOGICAL_GATEWAY": false,
   "LS_CT_SKIP_DST_LPORT_IPS": true,
   "LS_DNAT_MOD_DL_DST": true,
   "OVSDB_CON_TIMEOUT": 3,
   "OVSDB_INACTIVITY_TIMEOUT": 10,
-  "SECURE_SERVING": false,
   "SET_VXLAN_TX_OFF": false,
-  "U2O_INTERCONNECTION": false
+  "enableExternalVpcs": false,
+  "enableHardwareOffload": false,
+  "enableKeepVmIps": true,
+  "enableLiveMigrationOptimization": true,
+  "enableLoadbalancer": true,
+  "enableLoadbalancerService": false,
+  "enableNatGateways": true,
+  "enableNetworkPolicies": true,
+  "enableOvnInterconnections": false,
+  "enableOvnIpsec": false,
+  "enableSecureServing": false,
+  "enableTproxy": false,
+  "enableU2OInterconnections": false
 }
 
Features of Kube-OVN we wish to enable/disable.
-

Global parameters

- - - - - - - - - - + + - + - - + + - + - - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
fullnameOverridestringfeatures.enableExternalVpcsbool
-""
+false
 
Full name override.Enable external VPCs
globalobjectfeatures.enableHardwareOffloadbool
-{
-  "images": {
-    "kubeovn": {
-      "repository": "kube-ovn",
-      "support_arm": true,
-      "tag": "v1.14.0",
-      "thirdparty": true,
-      "vpcRepository": "vpc-nat-gateway"
-    }
-  },
-  "registry": {
-    "address": "docker.io/kubeovn",
-    "imagePullSecrets": []
-  }
-}
+false
 
Global configuration.Enable hardware offloads
imageobject
-"{}"
+			
features.enableKeepVmIpsbool
+true
 
Image configuration.Enable persistent VM IPs
image.pullPolicystringfeatures.enableLiveMigrationOptimizationbool
-"IfNotPresent"
+true
 
Pull policy for all images.Enable optimized live migrations for VMs
masterNodesstringfeatures.enableLoadbalancerbool
-""
+true
 
Comma-separated list of IPs for each master node.Enable Kube-OVN loadbalancers
masterNodesLabelstringfeatures.enableLoadbalancerServicebool
-"kube-ovn/role=master"
+false
 
Label used to auto-identify masters.Enable Kube-OVN loadbalancer services
nameOverridestringfeatures.enableNatGatewaysbool
-""
+true
 
Name override.Enable NAT gateways
namespacestringfeatures.enableNetworkPoliciesbool
-"kube-system"
+true
 
Namespace in which the CNI is deployed.Enable Kube-OVN network policies
clusterDomainstringfeatures.enableOvnInterconnectionsbool
-"cluster.local"
+false
 
Domain used by the cluster.Enable OVN interconnections
features.enableOvnIpsecbool
+false
+
+
Enable IPSEC
features.enableSecureServingbool
+false
+
+
Enable secure serving
features.enableTproxybool
+false
+
+
Enable TProxy
features.enableU2OInterconnectionsbool
+false
+
+
Enable underlay to overlay interconnections
@@ -1027,7 +1154,7 @@ false natGw.bgpSpeaker.image.tag string
-"v1.14.0"
+"v1.15.0"
 
Image tag. @@ -1043,6 +1170,35 @@ false +

Network Policies

+ + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
networkPoliciesobject
+"{}"
+
+
Configuration for network policies
networkPolicies.enforcementstring
+"standard"
+
+
Enforcement level of network policies when they get applied (can be: standard, lax). Enforcement "standard" blocks everything except what is allowed by the network policies. Enforcement "lax" is similar to "standard" with the exception that ARP/DHCPv4/DHCPv6/ICMPv4/ICMPv6 is allowed by default. This mode is useful when using Kubevirt and VMs with IPs configured via Kube-OVN's DHCP.

Network parameters of the CNI

@@ -1241,6 +1397,24 @@ false + + + + + + + + + + + + @@ -1390,7 +1564,7 @@ false "{}" - + @@ -1401,15 +1575,6 @@ false - - - - - - @@ -1429,6 +1594,15 @@ false + + + + + + @@ -1762,3 +1936,24 @@ false
IPv6 CIDR.
networking.pods.enableGatewayChecksbool
+true
+
+
Enable default gateway checks
networking.pods.enableLogicalGatewaysbool
+false
+
+
Enable logical gateways
networking.pods.gateways object DPDK-hybrid support for OVS. ref: https://kubeovn.github.io/docs/v1.13.x/en/advance/dpdk/DPDK-hybrid support for OVS. ref: https://kubeovn.github.io/docs/v1.12.x/en/advance/dpdk/
ovsOvn.dpdkHybrid.enabled Enables DPDK-hybrid support on OVS.
ovsOvn.dpdkHybrid.tagstring
-"v1.14.0-dpdk"
-
-
DPDK image tag.
ovsOvn.dpdkHybrid.resources object ovs-ovn resource limits & requests when DPDK-hybrid is enabled. ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
ovsOvn.dpdkHybrid.tagstring
+"v1.14.0-dpdk"
+
+
DPDK image tag.
ovsOvn.labels object
+

Other Values

+ + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
ovsOvn.ovsIpsecKeysDirectorystring
+"/etc/origin/ovs_ipsec_keys"
+
+
Directory on the node where Open vSwitch (OVS) IPSEC keys live.
+ diff --git a/charts/kube-ovn-v2/templates/controller/controller-deployment.yaml b/charts/kube-ovn-v2/templates/controller/controller-deployment.yaml index d73e3231cd9..19402d78629 100644 --- a/charts/kube-ovn-v2/templates/controller/controller-deployment.yaml +++ b/charts/kube-ovn-v2/templates/controller/controller-deployment.yaml @@ -160,6 +160,7 @@ spec: - --enable-ovn-lb-prefer-local={{- .Values.features.ENABLE_OVN_LB_PREFER_LOCAL }} - --image={{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} - --non-primary-cni-mode={{- .Values.cni.nonPrimaryCNI }} + - --np-enforcement={{- .Values.networkPolicies.enforcement | quote }} securityContext: runAsUser: {{ include "kubeovn.runAsUser" . }} privileged: false diff --git a/charts/kube-ovn-v2/values.yaml b/charts/kube-ovn-v2/values.yaml index a3f3d53b420..fa7d6719e66 100644 --- a/charts/kube-ovn-v2/values.yaml +++ b/charts/kube-ovn-v2/values.yaml @@ -312,6 +312,18 @@ natGw: # @section -- NAT gateways configuration apiNadProvider: "{{ .Values.apiNad.name }}.{{ .Values.namespace }}.ovn" + +# -- Configuration for network policies +# @section -- Network Policies +# @default -- "{}" +networkPolicies: + # -- Enforcement level of network policies when they get applied (can be: standard, lax). + # Enforcement "standard" blocks everything except what is allowed by the network policies. + # Enforcement "lax" is similar to "standard" with the exception that ARP/DHCPv4/DHCPv6/ICMPv4/ICMPv6 + # is allowed by default. This mode is useful when using Kubevirt and VMs with IPs configured via Kube-OVN's DHCP. + # @section -- Network Policies + enforcement: "standard" + # -- API NetworkAttachmentDefinition to give some pods (CoreDNS, NAT GW) in custom VPCs access to the K8S API. # This requires Multus to be installed. # @section -- API Network Attachment Definition configuration diff --git a/charts/kube-ovn/templates/controller-deploy.yaml b/charts/kube-ovn/templates/controller-deploy.yaml index a92198f8230..79af7c22292 100644 --- a/charts/kube-ovn/templates/controller-deploy.yaml +++ b/charts/kube-ovn/templates/controller-deploy.yaml @@ -121,6 +121,7 @@ spec: - --pod-nic-type={{- .Values.networking.POD_NIC_TYPE }} - --enable-lb={{- .Values.func.ENABLE_LB }} - --enable-np={{- .Values.func.ENABLE_NP }} + - --np-enforcement={{- .Values.func.NP_ENFORCEMENT }} - --enable-eip-snat={{- .Values.networking.ENABLE_EIP_SNAT }} - --enable-external-vpc={{- .Values.func.ENABLE_EXTERNAL_VPC }} - --enable-ecmp={{- .Values.networking.ENABLE_ECMP }} diff --git a/charts/kube-ovn/values.yaml b/charts/kube-ovn/values.yaml index 26944971645..58f913e8a80 100644 --- a/charts/kube-ovn/values.yaml +++ b/charts/kube-ovn/values.yaml @@ -57,6 +57,7 @@ networking: func: ENABLE_LB: true ENABLE_NP: true + NP_ENFORCEMENT: standard ENABLE_EXTERNAL_VPC: false HW_OFFLOAD: false ENABLE_LB_SVC: false diff --git a/mocks/pkg/ovs/interface.go b/mocks/pkg/ovs/interface.go index 7e355df1b16..15a27057e7b 100644 --- a/mocks/pkg/ovs/interface.go +++ b/mocks/pkg/ovs/interface.go @@ -2064,18 +2064,33 @@ func (mr *MockACLMockRecorder) UpdateAnpRuleACLOps(pgName, asName, protocol, acl } // UpdateDefaultBlockACLOps mocks base method. -func (m *MockACL) UpdateDefaultBlockACLOps(netpol, pgName, direction string, loggingEnabled bool) ([]ovsdb.Operation, error) { +func (m *MockACL) UpdateDefaultBlockACLOps(npName, pgName, direction string, loggingEnabled, lax bool) ([]ovsdb.Operation, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateDefaultBlockACLOps", netpol, pgName, direction, loggingEnabled) + ret := m.ctrl.Call(m, "UpdateDefaultBlockACLOps", npName, pgName, direction, loggingEnabled, lax) ret0, _ := ret[0].([]ovsdb.Operation) ret1, _ := ret[1].(error) return ret0, ret1 } // UpdateDefaultBlockACLOps indicates an expected call of UpdateDefaultBlockACLOps. -func (mr *MockACLMockRecorder) UpdateDefaultBlockACLOps(netpol, pgName, direction, loggingEnabled any) *gomock.Call { +func (mr *MockACLMockRecorder) UpdateDefaultBlockACLOps(npName, pgName, direction, loggingEnabled, lax any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultBlockACLOps", reflect.TypeOf((*MockACL)(nil).UpdateDefaultBlockACLOps), netpol, pgName, direction, loggingEnabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultBlockACLOps", reflect.TypeOf((*MockACL)(nil).UpdateDefaultBlockACLOps), npName, pgName, direction, loggingEnabled, lax) +} + +// UpdateDefaultBlockExceptionsACLOps mocks base method. +func (m *MockACL) UpdateDefaultBlockExceptionsACLOps(npName, pgName, npNamespace, direction string) ([]ovsdb.Operation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDefaultBlockExceptionsACLOps", npName, pgName, npNamespace, direction) + ret0, _ := ret[0].([]ovsdb.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateDefaultBlockExceptionsACLOps indicates an expected call of UpdateDefaultBlockExceptionsACLOps. +func (mr *MockACLMockRecorder) UpdateDefaultBlockExceptionsACLOps(npName, pgName, npNamespace, direction any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultBlockExceptionsACLOps", reflect.TypeOf((*MockACL)(nil).UpdateDefaultBlockExceptionsACLOps), npName, pgName, npNamespace, direction) } // UpdateEgressACLOps mocks base method. @@ -5237,18 +5252,33 @@ func (mr *MockNbClientMockRecorder) UpdateDHCPOptions(subnet, mtu any) *gomock.C } // UpdateDefaultBlockACLOps mocks base method. -func (m *MockNbClient) UpdateDefaultBlockACLOps(netpol, pgName, direction string, loggingEnabled bool) ([]ovsdb.Operation, error) { +func (m *MockNbClient) UpdateDefaultBlockACLOps(npName, pgName, direction string, loggingEnabled, lax bool) ([]ovsdb.Operation, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateDefaultBlockACLOps", netpol, pgName, direction, loggingEnabled) + ret := m.ctrl.Call(m, "UpdateDefaultBlockACLOps", npName, pgName, direction, loggingEnabled, lax) ret0, _ := ret[0].([]ovsdb.Operation) ret1, _ := ret[1].(error) return ret0, ret1 } // UpdateDefaultBlockACLOps indicates an expected call of UpdateDefaultBlockACLOps. -func (mr *MockNbClientMockRecorder) UpdateDefaultBlockACLOps(netpol, pgName, direction, loggingEnabled any) *gomock.Call { +func (mr *MockNbClientMockRecorder) UpdateDefaultBlockACLOps(npName, pgName, direction, loggingEnabled, lax any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultBlockACLOps", reflect.TypeOf((*MockNbClient)(nil).UpdateDefaultBlockACLOps), npName, pgName, direction, loggingEnabled, lax) +} + +// UpdateDefaultBlockExceptionsACLOps mocks base method. +func (m *MockNbClient) UpdateDefaultBlockExceptionsACLOps(npName, pgName, npNamespace, direction string) ([]ovsdb.Operation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDefaultBlockExceptionsACLOps", npName, pgName, npNamespace, direction) + ret0, _ := ret[0].([]ovsdb.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateDefaultBlockExceptionsACLOps indicates an expected call of UpdateDefaultBlockExceptionsACLOps. +func (mr *MockNbClientMockRecorder) UpdateDefaultBlockExceptionsACLOps(npName, pgName, npNamespace, direction any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultBlockACLOps", reflect.TypeOf((*MockNbClient)(nil).UpdateDefaultBlockACLOps), netpol, pgName, direction, loggingEnabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultBlockExceptionsACLOps", reflect.TypeOf((*MockNbClient)(nil).UpdateDefaultBlockExceptionsACLOps), npName, pgName, npNamespace, direction) } // UpdateDnatAndSnat mocks base method. diff --git a/pkg/controller/config.go b/pkg/controller/config.go index de90d5e2a56..cff2cb279fd 100644 --- a/pkg/controller/config.go +++ b/pkg/controller/config.go @@ -128,6 +128,9 @@ type Configuration struct { // Non Primary CNI flag EnableNonPrimaryCNI bool + + // Enforcement level of network policies (standard, lax) + NetworkPolicyEnforcement string } // ParseFlags parses cmd args then init kubeclient and conf @@ -184,6 +187,7 @@ func ParseFlags() (*Configuration, error) { argPodNicType = pflag.String("pod-nic-type", "veth-pair", "The default pod network nic implementation type") argEnableLb = pflag.Bool("enable-lb", true, "Enable load balancer") argEnableNP = pflag.Bool("enable-np", true, "Enable network policy support") + argNPEnforcement = pflag.String("np-enforcement", "standard", "Network policy enforcement (standard, lax), default is standard") argEnableEipSnat = pflag.Bool("enable-eip-snat", true, "Enable EIP and SNAT") argEnableExternalVpc = pflag.Bool("enable-external-vpc", false, "Enable external vpc support") argEnableEcmp = pflag.Bool("enable-ecmp", false, "Enable ecmp route for centralized subnet") @@ -312,6 +316,7 @@ func ParseFlags() (*Configuration, error) { TLSMaxVersion: *argTLSMaxVersion, TLSCipherSuites: *argTLSCipherSuites, EnableNonPrimaryCNI: *argNonPrimaryCNI, + NetworkPolicyEnforcement: *argNPEnforcement, } if config.OvsDbConnectTimeout >= config.OvsDbInactivityTimeout { return nil, errors.New("OVS DB inactivity timeout value should be greater than reconnect timeout value") diff --git a/pkg/controller/network_policy.go b/pkg/controller/network_policy.go index 51804e0fe4a..09a437b5a15 100644 --- a/pkg/controller/network_policy.go +++ b/pkg/controller/network_policy.go @@ -26,6 +26,11 @@ import ( "github.com/kubeovn/kube-ovn/pkg/util" ) +const ( + NetworkPolicyEnforcementStandard = "standard" + NetworkPolicyEnforcementLax = "lax" +) + func (c *Controller) enqueueAddNp(obj any) { key := cache.MetaObjectToName(obj.(*netv1.NetworkPolicy)).String() klog.V(3).Infof("enqueue add network policy %s", key) @@ -174,12 +179,23 @@ func (c *Controller) handleUpdateNp(key string) error { if hasIngressRule(np) { if protocolSet.Size() > 0 { - blockACLOps, err := c.OVNNbClient.UpdateDefaultBlockACLOps(key, pgName, ovnnb.ACLDirectionToLport, logEnable) + enforcementLax := c.isNetworkPolicyEnforcementLax(np) + + blockACLOps, err := c.OVNNbClient.UpdateDefaultBlockACLOps(key, pgName, ovnnb.ACLDirectionToLport, logEnable, enforcementLax) if err != nil { klog.Errorf("failed to set default ingress block acl: %v", err) return fmt.Errorf("failed to set default ingress block acl: %w", err) } ingressACLOps = append(ingressACLOps, blockACLOps...) + + if enforcementLax { + defaultBlockExceptions, err := c.OVNNbClient.UpdateDefaultBlockExceptionsACLOps(key, pgName, np.Namespace, ovnnb.ACLDirectionToLport) + if err != nil { + klog.Errorf("failed to set default block exceptions for ingress acl: %v", err) + return fmt.Errorf("failed to set default block exceptions for ingress acl: %w", err) + } + ingressACLOps = append(ingressACLOps, defaultBlockExceptions...) + } } for _, protocol := range protocolSet.List() { @@ -312,12 +328,23 @@ func (c *Controller) handleUpdateNp(key string) error { if hasEgressRule(np) { if protocolSet.Size() > 0 { - blockACLOps, err := c.OVNNbClient.UpdateDefaultBlockACLOps(key, pgName, ovnnb.ACLDirectionFromLport, logEnable) + enforcementLax := c.isNetworkPolicyEnforcementLax(np) + + blockACLOps, err := c.OVNNbClient.UpdateDefaultBlockACLOps(key, pgName, ovnnb.ACLDirectionFromLport, logEnable, enforcementLax) if err != nil { klog.Errorf("failed to set default egress block acl: %v", err) return fmt.Errorf("failed to set default egress block acl: %w", err) } egressACLOps = append(egressACLOps, blockACLOps...) + + if enforcementLax { + defaultBlockExceptions, err := c.OVNNbClient.UpdateDefaultBlockExceptionsACLOps(key, pgName, np.Namespace, ovnnb.ACLDirectionFromLport) + if err != nil { + klog.Errorf("failed to set default block exceptions for ingress acl: %v", err) + return fmt.Errorf("failed to set default block exceptions for ingress acl: %w", err) + } + egressACLOps = append(egressACLOps, defaultBlockExceptions...) + } } for _, protocol := range protocolSet.List() { @@ -794,3 +821,13 @@ func isNamespaceMatchNetworkPolicy(ns *corev1.Namespace, policy *netv1.NetworkPo } return false } + +func (c *Controller) isNetworkPolicyEnforcementLax(policy *netv1.NetworkPolicy) bool { + // User provided a custom enforcement through annotations + if value, ok := policy.Annotations[util.NetworkPolicyEnforcementAnnotation]; ok { + return value == NetworkPolicyEnforcementLax + } + + // Fallback to the configuration of the controller + return c.config.NetworkPolicyEnforcement == NetworkPolicyEnforcementLax +} diff --git a/pkg/ovs/interface.go b/pkg/ovs/interface.go index 250edbd6907..c8ec84985c3 100644 --- a/pkg/ovs/interface.go +++ b/pkg/ovs/interface.go @@ -158,7 +158,8 @@ type PortGroup interface { } type ACL interface { - UpdateDefaultBlockACLOps(netpol, pgName, direction string, loggingEnabled bool) ([]ovsdb.Operation, error) + UpdateDefaultBlockACLOps(npName, pgName, direction string, loggingEnabled, lax bool) ([]ovsdb.Operation, error) + UpdateDefaultBlockExceptionsACLOps(npName, pgName, npNamespace, direction string) ([]ovsdb.Operation, error) UpdateIngressACLOps(pgName, asIngressName, asExceptName, protocol, aclName string, npp []netv1.NetworkPolicyPort, logEnable bool, logACLActions []ovnnb.ACLAction, namedPortMap map[string]*util.NamedPortInfo) ([]ovsdb.Operation, error) UpdateEgressACLOps(pgName, asEgressName, asExceptName, protocol, aclName string, npp []netv1.NetworkPolicyPort, logEnable bool, logACLActions []ovnnb.ACLAction, namedPortMap map[string]*util.NamedPortInfo) ([]ovsdb.Operation, error) CreateGatewayACL(lsName, pgName, gateway, u2oInterconnectionIP string) error diff --git a/pkg/ovs/ovn-nb-acl.go b/pkg/ovs/ovn-nb-acl.go index efcf844b432..b90dac7144c 100644 --- a/pkg/ovs/ovn-nb-acl.go +++ b/pkg/ovs/ovn-nb-acl.go @@ -54,7 +54,7 @@ func setACLName(acl *ovnnb.ACL, name string) { } // UpdateDefaultBlockACLOps returns operations to update/create the default block ACL -func (c *OVNNbClient) UpdateDefaultBlockACLOps(netpol, pgName, direction string, loggingEnabled bool) ([]ovsdb.Operation, error) { +func (c *OVNNbClient) UpdateDefaultBlockACLOps(npName, pgName, direction string, loggingEnabled, lax bool) ([]ovsdb.Operation, error) { portDirection := "outport" priority := util.IngressDefaultDrop @@ -63,14 +63,24 @@ func (c *OVNNbClient) UpdateDefaultBlockACLOps(netpol, pgName, direction string, priority = util.EgressDefaultDrop } - // Block everything IP related (IPv4/IPv6/ICMPv4/ICMPv6/...) - allIPMatch := NewAndACLMatch( - NewACLMatch(portDirection, "==", "@"+pgName, ""), - NewACLMatch("ip", "", "", ""), - ) + var match ACLMatch + + if lax { + // This is the "lax" enforcement mode, we block only TCP/UDP/SCTP + match = NewAndACLMatch( + NewACLMatch(portDirection, "==", "@"+pgName, ""), + NewACLMatch("(tcp || udp || sctp)", "", "", ""), + ) + } else { + // This is the "standard" enforcement mode, we block everything IP related (IPv4/IPv6/ICMPv4/ICMPv6/...) + match = NewAndACLMatch( + NewACLMatch(portDirection, "==", "@"+pgName, ""), + NewACLMatch("ip", "", "", ""), + ) + } options := func(acl *ovnnb.ACL) { - setACLName(acl, netpol) + setACLName(acl, npName) if loggingEnabled { acl.Log = true acl.Severity = ptr.To(ovnnb.ACLSeverityWarning) @@ -84,7 +94,7 @@ func (c *OVNNbClient) UpdateDefaultBlockACLOps(netpol, pgName, direction string, } } - defaultDropACL, err := c.newACLWithoutCheck(pgName, direction, priority, allIPMatch.String(), ovnnb.ACLActionDrop, util.NetpolACLTier, options) + defaultDropACL, err := c.newACLWithoutCheck(pgName, direction, priority, match.String(), ovnnb.ACLActionDrop, util.NetpolACLTier, options) if err != nil { klog.Error(err) return nil, fmt.Errorf("failed to create drop acl for port group %s: %w", pgName, err) @@ -99,6 +109,60 @@ func (c *OVNNbClient) UpdateDefaultBlockACLOps(netpol, pgName, direction string, return ops, nil } +// UpdateDefaultBlockExceptionsACLOps updates the exceptions to the default block ACLs of a NetworkPolicy to allow DHCPv4/DHCPv6. +func (c *OVNNbClient) UpdateDefaultBlockExceptionsACLOps(npName, pgName, npNamespace, direction string) ([]ovsdb.Operation, error) { + portDirection := "outport" + dhcpv4UdpSrc, dhcpv4UdpDst := "67", "68" + dhcpv6UdpSrc, dhcpv6UdpDst := "547", "546" + + if direction == ovnnb.ACLDirectionFromLport { // Egress rule + portDirection = "inport" + dhcpv4UdpSrc, dhcpv4UdpDst = dhcpv4UdpDst, dhcpv4UdpSrc + dhcpv6UdpSrc, dhcpv6UdpDst = dhcpv6UdpDst, dhcpv6UdpSrc + } + + acls := make([]*ovnnb.ACL, 0) + + newACL := func(match string) { + options := func(acl *ovnnb.ACL) { + setACLName(acl, npName) + } + + acl, err := c.newACL(pgName, direction, util.IngressAllowPriority, match, ovnnb.ACLActionAllowRelated, util.NetpolACLTier, options) + if err != nil { + klog.Error(err) + klog.Errorf("failed to create new block exceptions acl for network policy %s/%s: %v", npNamespace, npName, err) + return + } + acls = append(acls, acl) + } + + // Allow DHCPv6 + dhcpv6Match := NewAndACLMatch( + NewACLMatch(portDirection, "==", "@"+pgName, ""), + NewACLMatch("udp.src", "==", dhcpv6UdpSrc, ""), + NewACLMatch("udp.dst", "==", dhcpv6UdpDst, ""), + NewACLMatch("ip6", "", "", ""), + ) + newACL(dhcpv6Match.String()) + + // Allow DHCPv4 + dhcpv4Match := NewAndACLMatch( + NewACLMatch(portDirection, "==", "@"+pgName, ""), + NewACLMatch("udp.src", "==", dhcpv4UdpSrc, ""), + NewACLMatch("udp.dst", "==", dhcpv4UdpDst, ""), + NewACLMatch("ip4", "", "", ""), + ) + newACL(dhcpv4Match.String()) + + ops, err := c.CreateAclsOps(pgName, portGroupKey, acls...) + if err != nil { + klog.Error(err) + return nil, fmt.Errorf("failed to create block exceptions acl for port group %s: %w", pgName, err) + } + return ops, nil +} + // UpdateIngressACLOps return operation that creates an ingress ACL func (c *OVNNbClient) UpdateIngressACLOps(pgName, asIngressName, asExceptName, protocol, aclName string, npp []netv1.NetworkPolicyPort, logEnable bool, logACLActions []ovnnb.ACLAction, namedPortMap map[string]*util.NamedPortInfo) ([]ovsdb.Operation, error) { acls := make([]*ovnnb.ACL, 0) diff --git a/pkg/ovs/ovn-nb-acl_test.go b/pkg/ovs/ovn-nb-acl_test.go index 7ba65019a84..b6ca90d7f5a 100644 --- a/pkg/ovs/ovn-nb-acl_test.go +++ b/pkg/ovs/ovn-nb-acl_test.go @@ -65,6 +65,56 @@ func newACL(parentName, direction, priority, match, action string, tier int, opt return acl } +func (suite *OvnClientTestSuite) testUpdateDefaultBlockExceptionsACLOps() { + t := suite.T() + t.Parallel() + + nbClient := suite.ovnNBClient + + expect := func(row ovsdb.Row, action, direction, match, priority string) { + intPriority, err := strconv.Atoi(priority) + require.NoError(t, err) + require.Equal(t, action, row["action"]) + require.Equal(t, direction, row["direction"]) + require.Equal(t, match, row["match"]) + require.Equal(t, intPriority, row["priority"]) + } + + t.Run("ingress exceptions", func(t *testing.T) { + t.Parallel() + + netpol := "ingress exceptions" + pgName := "test_ingress_exceptions" + + err := nbClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + ops, err := nbClient.UpdateDefaultBlockExceptionsACLOps(netpol, pgName, "default", ovnnb.ACLDirectionToLport) + require.NoError(t, err) + require.Len(t, ops, 3) + + expect(ops[0].Row, "allow-related", ovnnb.ACLDirectionToLport, fmt.Sprintf("outport == @%s && udp.src == 547 && udp.dst == 546 && ip6", pgName), util.IngressAllowPriority) + expect(ops[1].Row, "allow-related", ovnnb.ACLDirectionToLport, fmt.Sprintf("outport == @%s && udp.src == 67 && udp.dst == 68 && ip4", pgName), util.IngressAllowPriority) + }) + + t.Run("egress exceptions", func(t *testing.T) { + t.Parallel() + + netpol := "egress exceptions" + pgName := "test_egress_exceptions" + + err := nbClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + ops, err := nbClient.UpdateDefaultBlockExceptionsACLOps(netpol, pgName, "default", ovnnb.ACLDirectionFromLport) + require.NoError(t, err) + require.Len(t, ops, 3) + + expect(ops[0].Row, "allow-related", ovnnb.ACLDirectionFromLport, fmt.Sprintf("inport == @%s && udp.src == 546 && udp.dst == 547 && ip6", pgName), util.EgressAllowPriority) + expect(ops[1].Row, "allow-related", ovnnb.ACLDirectionFromLport, fmt.Sprintf("inport == @%s && udp.src == 68 && udp.dst == 67 && ip4", pgName), util.EgressAllowPriority) + }) +} + func (suite *OvnClientTestSuite) testUpdateDefaultBlockACLOps() { t := suite.T() t.Parallel() @@ -89,7 +139,7 @@ func (suite *OvnClientTestSuite) testUpdateDefaultBlockACLOps() { err := nbClient.CreatePortGroup(pgName, nil) require.NoError(t, err) - ops, err := nbClient.UpdateDefaultBlockACLOps(netpol, pgName, ovnnb.ACLDirectionToLport, true) + ops, err := nbClient.UpdateDefaultBlockACLOps(netpol, pgName, ovnnb.ACLDirectionToLport, true, false) require.NoError(t, err) require.Len(t, ops, 2) @@ -105,12 +155,44 @@ func (suite *OvnClientTestSuite) testUpdateDefaultBlockACLOps() { err := nbClient.CreatePortGroup(pgName, nil) require.NoError(t, err) - ops, err := nbClient.UpdateDefaultBlockACLOps(netpol, pgName, ovnnb.ACLDirectionFromLport, true) + ops, err := nbClient.UpdateDefaultBlockACLOps(netpol, pgName, ovnnb.ACLDirectionFromLport, true, false) require.NoError(t, err) require.Len(t, ops, 2) expect(ops[0].Row, "drop", ovnnb.ACLDirectionFromLport, fmt.Sprintf("inport == @%s && ip", pgName), util.EgressDefaultDrop) }) + + t.Run("lax default block ingress", func(t *testing.T) { + t.Parallel() + + netpol := "lax default block ingress" + pgName := "test_create_lax_block_ingress_acl_pg" + + err := nbClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + ops, err := nbClient.UpdateDefaultBlockACLOps(netpol, pgName, ovnnb.ACLDirectionToLport, true, true) + require.NoError(t, err) + require.Len(t, ops, 2) + + expect(ops[0].Row, "drop", ovnnb.ACLDirectionToLport, fmt.Sprintf("outport == @%s && (tcp || udp || sctp)", pgName), util.IngressDefaultDrop) + }) + + t.Run("lax default block egress", func(t *testing.T) { + t.Parallel() + + netpol := "lax default block egress" + pgName := "test_create_lax_block_egress_acl_pg" + + err := nbClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + ops, err := nbClient.UpdateDefaultBlockACLOps(netpol, pgName, ovnnb.ACLDirectionFromLport, true, true) + require.NoError(t, err) + require.Len(t, ops, 2) + + expect(ops[0].Row, "drop", ovnnb.ACLDirectionFromLport, fmt.Sprintf("inport == @%s && (tcp || udp || sctp)", pgName), util.EgressDefaultDrop) + }) } func (suite *OvnClientTestSuite) testUpdateIngressACLOps() { diff --git a/pkg/ovs/ovn-nb-suite_test.go b/pkg/ovs/ovn-nb-suite_test.go index 635b78b4333..57a774c6630 100644 --- a/pkg/ovs/ovn-nb-suite_test.go +++ b/pkg/ovs/ovn-nb-suite_test.go @@ -707,6 +707,10 @@ func (suite *OvnClientTestSuite) Test_testUpdateDefaultBlockAclOps() { suite.testUpdateDefaultBlockACLOps() } +func (suite *OvnClientTestSuite) Test_testUpdateDefaultBlockExceptionsACLOps() { + suite.testUpdateDefaultBlockExceptionsACLOps() +} + func (suite *OvnClientTestSuite) Test_testUpdateIngressAclOps() { suite.testUpdateIngressACLOps() } diff --git a/pkg/util/const.go b/pkg/util/const.go index aec5c5eb2ca..27daf9c21dd 100644 --- a/pkg/util/const.go +++ b/pkg/util/const.go @@ -100,20 +100,21 @@ const ( OvsDpTypeLabel = "ovn.kubernetes.io/ovs_dp_type" - VpcNameLabel = "ovn.kubernetes.io/vpc" - SubnetNameLabel = "ovn.kubernetes.io/subnet" - ICGatewayLabel = "ovn.kubernetes.io/ic-gw" - ExGatewayLabel = "ovn.kubernetes.io/external-gw" - NodeExtGwLabel = "ovn.kubernetes.io/node-ext-gw" - VpcNatGatewayLabel = "ovn.kubernetes.io/vpc-nat-gw" - IPReservedLabel = "ovn.kubernetes.io/ip_reserved" - VpcNatGatewayNameLabel = "ovn.kubernetes.io/vpc-nat-gw-name" - VpcLbLabel = "ovn.kubernetes.io/vpc_lb" - VpcDNSNameLabel = "ovn.kubernetes.io/vpc-dns" - QoSLabel = "ovn.kubernetes.io/qos" - NodeNameLabel = "ovn.kubernetes.io/node-name" - NetworkPolicyLogAnnotation = "ovn.kubernetes.io/enable_log" - ACLActionsLogAnnotation = "ovn.kubernetes.io/log_acl_actions" + VpcNameLabel = "ovn.kubernetes.io/vpc" + SubnetNameLabel = "ovn.kubernetes.io/subnet" + ICGatewayLabel = "ovn.kubernetes.io/ic-gw" + ExGatewayLabel = "ovn.kubernetes.io/external-gw" + NodeExtGwLabel = "ovn.kubernetes.io/node-ext-gw" + VpcNatGatewayLabel = "ovn.kubernetes.io/vpc-nat-gw" + IPReservedLabel = "ovn.kubernetes.io/ip_reserved" + VpcNatGatewayNameLabel = "ovn.kubernetes.io/vpc-nat-gw-name" + VpcLbLabel = "ovn.kubernetes.io/vpc_lb" + VpcDNSNameLabel = "ovn.kubernetes.io/vpc-dns" + QoSLabel = "ovn.kubernetes.io/qos" + NodeNameLabel = "ovn.kubernetes.io/node-name" + NetworkPolicyLogAnnotation = "ovn.kubernetes.io/enable_log" + NetworkPolicyEnforcementAnnotation = "ovn.kubernetes.io/network_policy_enforcement" + ACLActionsLogAnnotation = "ovn.kubernetes.io/log_acl_actions" VpcEgressGatewayLabel = "ovn.kubernetes.io/vpc-egress-gateway" GenerateHashAnnotation = "ovn.kubernetes.io/generate-hash"