Skip to content

Commit

Permalink
Add nodeSelector to Egress Firewall
Browse files Browse the repository at this point in the history
This allows destination addresses to be chosen via node selector for
egress firewall rules and addresses the use case where a user wants to
open holes in the firewall to specific nodes. For example, if a user
wants to enable kapi service accesss to host networked kapi server
endpoints.

Signed-off-by: Tim Rozet <trozet@redhat.com>
  • Loading branch information
trozet committed Feb 27, 2023
1 parent cfae31d commit 1a331d9
Show file tree
Hide file tree
Showing 13 changed files with 835 additions and 38 deletions.
57 changes: 52 additions & 5 deletions dist/templates/k8s.ovn.org_egressfirewalls.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.11.3
controller-gen.kubebuilder.io/version: v0.7.0
creationTimestamp: null
name: egressfirewalls.k8s.ovn.org
spec:
Expand Down Expand Up @@ -82,16 +82,63 @@ spec:
properties:
cidrSelector:
description: cidrSelector is the CIDR range to allow/deny
traffic to. If this is set, dnsName must be unset.
traffic to. If this is set, dnsName and nodeSelector must
be unset.
type: string
dnsName:
description: dnsName is the domain name to allow/deny traffic
to. If this is set, cidrSelector must be unset.
to. If this is set, cidrSelector and nodeSelector must
be unset.
pattern: ^([A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\.?$
type: string
nodeSelector:
description: nodeSelector will allow/deny traffic to the
Kubernetes node IP of selected nodes. If this is, set
cidrSelector and DNSName must be unset.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values array
must be non-empty. If the operator is Exists
or DoesNotExist, the values array must be empty.
This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field
is "key", the operator is "In", and the values array
contains only "value". The requirements are ANDed.
type: object
type: object
type: object
minProperties: 1
maxProperties: 1
type:
description: type marks this as an "Allow" or "Deny" rule
pattern: ^Allow|Deny$
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions go-controller/pkg/crd/egressfirewall/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,15 @@ type EgressFirewallPort struct {

// EgressFirewallDestination is the endpoint that traffic is either allowed or denied to
type EgressFirewallDestination struct {
// cidrSelector is the CIDR range to allow/deny traffic to. If this is set, dnsName must be unset.
// cidrSelector is the CIDR range to allow/deny traffic to. If this is set, dnsName and nodeSelector must be unset.
CIDRSelector string `json:"cidrSelector,omitempty"`
// dnsName is the domain name to allow/deny traffic to. If this is set, cidrSelector must be unset.
// dnsName is the domain name to allow/deny traffic to. If this is set, cidrSelector and nodeSelector must be unset.
// +kubebuilder:validation:Pattern=^([A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\.?$
DNSName string `json:"dnsName,omitempty"`
// nodeSelector will allow/deny traffic to the Kubernetes node IP of selected nodes. If this is, set
// cidrSelector and DNSName must be unset.
// +optional
NodeSelector metav1.LabelSelector `json:"nodeSelector,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions go-controller/pkg/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ type egressIPPod struct{}
type egressIPNamespace struct{}
type egressNode struct{}

// types for handlers related to egress Firewall
type egressFwNode struct{}

// types for handlers in use by ovn-k node
type namespaceExGw struct{}
type endpointSliceForStaleConntrackRemoval struct{}
Expand All @@ -119,6 +122,7 @@ var (
EgressIPNamespaceType reflect.Type = reflect.TypeOf(&egressIPNamespace{})
EgressIPPodType reflect.Type = reflect.TypeOf(&egressIPPod{})
EgressNodeType reflect.Type = reflect.TypeOf(&egressNode{})
EgressFwNodeType reflect.Type = reflect.TypeOf(&egressFwNode{})
CloudPrivateIPConfigType reflect.Type = reflect.TypeOf(&ocpcloudnetworkapi.CloudPrivateIPConfig{})
EgressQoSType reflect.Type = reflect.TypeOf(&egressqosapi.EgressQoS{})
PeerNamespaceAndPodSelectorType reflect.Type = reflect.TypeOf(&peerNamespaceAndPodSelector{})
Expand Down Expand Up @@ -431,7 +435,7 @@ type AddHandlerFuncType func(namespace string, sel labels.Selector, funcs cache.
// This is relevant only for handlers that are sharing the same resources:
// Pods: shared by PodType (0), EgressIPPodType (1), PeerPodSelectorType (2), PeerPodForNamespaceAndPodSelectorType (3), LocalPodSelectorType (4)
// Namespaces: shared by NamespaceType (0), EgressIPNamespaceType (1), PeerNamespaceSelectorType (3), PeerNamespaceAndPodSelectorType (4)
// Nodes: shared by NodeType (0), EgressNodeType (1)
// Nodes: shared by NodeType (0), EgressNodeType (1), EgressFwNodeType (1)
// By default handlers get the defaultHandlerPriority which is 0 (highest priority). Higher the number, lower the priority to get an event.
// Example: EgressIPPodType will always get the pod event after PodType and PeerPodSelectorType will always get the event after PodType and EgressIPPodType
// NOTE: If you are touching this function to add a new object type that uses shared objects, please make sure to update `minHandlerPriority` if needed
Expand All @@ -453,6 +457,8 @@ func (wf *WatchFactory) GetHandlerPriority(objType reflect.Type) (priority int)
return 3
case EgressNodeType:
return 1
case EgressFwNodeType:
return 1
default:
return defaultHandlerPriority
}
Expand All @@ -473,7 +479,7 @@ func (wf *WatchFactory) GetResourceHandlerFunc(objType reflect.Type) (AddHandler
return wf.AddPolicyHandler(funcs, processExisting)
}, nil

case NodeType, EgressNodeType:
case NodeType, EgressNodeType, EgressFwNodeType:
return func(namespace string, sel labels.Selector,
funcs cache.ResourceEventHandler, processExisting func([]interface{}) error) (*Handler, error) {
return wf.AddNodeHandler(funcs, processExisting, priority)
Expand Down Expand Up @@ -755,6 +761,15 @@ func (wf *WatchFactory) GetNode(name string) (*kapi.Node, error) {
return nodeLister.Get(name)
}

// GetNodesBySelector returns all the nodes selected by the label selector
func (wf *WatchFactory) GetNodesBySelector(labelSelector metav1.LabelSelector) ([]*kapi.Node, error) {
selector, err := metav1.LabelSelectorAsSelector(&labelSelector)
if err != nil {
return nil, err
}
return wf.ListNodes(selector)
}

// GetService returns the service spec of a service in a given namespace
func (wf *WatchFactory) GetService(namespace, name string) (*kapi.Service, error) {
serviceLister := wf.informers[ServiceType].lister.(listers.ServiceLister)
Expand Down
20 changes: 19 additions & 1 deletion go-controller/pkg/ovn/base_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func hasResourceAnUpdateFunc(objType reflect.Type) bool {
factory.EgressIPNamespaceType,
factory.EgressIPPodType,
factory.EgressNodeType,
factory.EgressFwNodeType,
factory.CloudPrivateIPConfigType,
factory.LocalPodSelectorType,
factory.NamespaceType:
Expand Down Expand Up @@ -106,6 +107,21 @@ func (h *baseNetworkControllerEventHandler) areResourcesEqual(objType reflect.Ty
// force update path for EgressIP resource.
return false, nil

case factory.EgressFwNodeType:
oldNode, ok := obj1.(*kapi.Node)
if !ok {
return false, fmt.Errorf("could not cast obj1 of type %T to *kapi.Node", obj1)
}
newNode, ok := obj2.(*kapi.Node)
if !ok {
return false, fmt.Errorf("could not cast obj2 of type %T to *kapi.Node", obj2)
}
if reflect.DeepEqual(oldNode.Labels, newNode.Labels) &&
reflect.DeepEqual(oldNode.Status.Addresses, newNode.Status.Addresses) {
return true, nil
}
return false, nil

case factory.NamespaceType:
// force update path for Namespace resource.
return false, nil
Expand All @@ -132,7 +148,8 @@ func (h *baseNetworkControllerEventHandler) getResourceFromInformerCache(objType
obj, err = watchFactory.GetNetworkPolicy(namespace, name)

case factory.NodeType,
factory.EgressNodeType:
factory.EgressNodeType,
factory.EgressFwNodeType:
obj, err = watchFactory.GetNode(name)

case factory.PodType,
Expand Down Expand Up @@ -179,6 +196,7 @@ func (h *baseNetworkControllerEventHandler) isResourceScheduled(objType reflect.
func needsUpdateDuringRetry(objType reflect.Type) bool {
switch objType {
case factory.EgressNodeType,
factory.EgressFwNodeType,
factory.EgressIPType,
factory.EgressIPPodType,
factory.EgressIPNamespaceType,
Expand Down
30 changes: 30 additions & 0 deletions go-controller/pkg/ovn/default_network_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ type DefaultNetworkController struct {
retryEgressIPPods *retry.RetryFramework
// retry framework for Egress nodes
retryEgressNodes *retry.RetryFramework
// retry framework for Egress Firewall Nodes
retryEgressFwNodes *retry.RetryFramework
// EgressIP Node-specific syncMap used by egressip node event handler
addEgressNodeFailed sync.Map

Expand Down Expand Up @@ -219,6 +221,7 @@ func (oc *DefaultNetworkController) initRetryFramework() {
oc.retryEgressIPNamespaces = oc.newRetryFrameworkWithParameters(factory.EgressIPNamespaceType, nil, nil)
oc.retryEgressIPPods = oc.newRetryFrameworkWithParameters(factory.EgressIPPodType, nil, nil)
oc.retryEgressNodes = oc.newRetryFrameworkWithParameters(factory.EgressNodeType, nil, nil)
oc.retryEgressFwNodes = oc.newRetryFrameworkWithParameters(factory.EgressFwNodeType, nil, nil)
oc.retryCloudPrivateIPConfig = oc.newRetryFrameworkWithParameters(factory.CloudPrivateIPConfigType, nil, nil)
oc.retryNamespaces = oc.newRetryFrameworkWithParameters(factory.NamespaceType, nil, nil)
}
Expand Down Expand Up @@ -434,6 +437,10 @@ func (oc *DefaultNetworkController) Run(ctx context.Context) error {
if err != nil {
return err
}
err = oc.WatchEgressFwNodes()
if err != nil {
return err
}
}

if config.OVNKubernetesFeature.EnableEgressQoS {
Expand Down Expand Up @@ -729,6 +736,14 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from
}
}

case factory.EgressFwNodeType:
node := obj.(*kapi.Node)
if err = h.oc.updateEgressFirewallForNode(nil, node); err != nil {
klog.Infof("Node add failed during egress firewall eval for node: %s, will try again later: %v",
node.Name, err)
return err
}

case factory.CloudPrivateIPConfigType:
cloudPrivateIPConfig := obj.(*ocpcloudnetworkapi.CloudPrivateIPConfig)
return h.oc.reconcileCloudPrivateIPConfig(nil, cloudPrivateIPConfig)
Expand Down Expand Up @@ -888,6 +903,11 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int
}
return nil

case factory.EgressFwNodeType:
oldNode := oldObj.(*kapi.Node)
newNode := newObj.(*kapi.Node)
return h.oc.updateEgressFirewallForNode(oldNode, newNode)

case factory.CloudPrivateIPConfigType:
oldCloudPrivateIPConfig := oldObj.(*ocpcloudnetworkapi.CloudPrivateIPConfig)
newCloudPrivateIPConfig := newObj.(*ocpcloudnetworkapi.CloudPrivateIPConfig)
Expand Down Expand Up @@ -986,6 +1006,13 @@ func (h *defaultNetworkControllerEventHandler) DeleteResource(obj, cachedObj int
}
return nil

case factory.EgressFwNodeType:
node, ok := obj.(*kapi.Node)
if !ok {
return fmt.Errorf("could not cast obj of type %T to *knet.Node", obj)
}
return h.oc.updateEgressFirewallForNode(node, nil)

case factory.CloudPrivateIPConfigType:
cloudPrivateIPConfig := obj.(*ocpcloudnetworkapi.CloudPrivateIPConfig)
return h.oc.reconcileCloudPrivateIPConfig(cloudPrivateIPConfig, nil)
Expand Down Expand Up @@ -1032,6 +1059,9 @@ func (h *defaultNetworkControllerEventHandler) SyncFunc(objs []interface{}) erro
case factory.EgressNodeType:
syncFunc = h.oc.initClusterEgressPolicies

case factory.EgressFwNodeType:
syncFunc = nil

case factory.EgressIPPodType,
factory.EgressIPType,
factory.CloudPrivateIPConfigType:
Expand Down

0 comments on commit 1a331d9

Please sign in to comment.