diff --git a/api/core/v1alpha1/common_types.go b/api/core/v1alpha1/common_types.go index c9b343a1..7488f293 100644 --- a/api/core/v1alpha1/common_types.go +++ b/api/core/v1alpha1/common_types.go @@ -4,8 +4,6 @@ package v1alpha1 import ( - "net/netip" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -52,22 +50,3 @@ type LocalUIDReference struct { // UID is the UID of the referenced entity. UID types.UID `json:"uid"` } - -// IPPrefix represents a network prefix. -// +nullable -type IPPrefix struct { - netip.Prefix `json:"-"` -} - -func (in *IPPrefix) DeepCopyInto(out *IPPrefix) { - *out = *in -} - -// IPAdd is an IP address. -type IPAdd struct { - netip.Addr `json:"-"` -} - -func (in *IPAdd) DeepCopyInto(out *IPAdd) { - *out = *in -} diff --git a/api/core/v1alpha1/networkpolicy_types.go b/api/core/v1alpha1/networkpolicy_types.go index 2a878ebd..2d3f9f7a 100644 --- a/api/core/v1alpha1/networkpolicy_types.go +++ b/api/core/v1alpha1/networkpolicy_types.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -9,7 +9,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NetworkPolicySpec defines the desired state of NetworkPolicy. type NetworkPolicySpec struct { // NetworkRef is the network to regulate using this policy. NetworkRef corev1.LocalObjectReference `json:"networkRef"` @@ -66,14 +65,14 @@ type NetworkPolicyPeer struct { // NetworkPolicyIngressRule describes a rule to regulate ingress traffic with. type NetworkPolicyIngressRule struct { - // Ports specifies the list of ports which should be made accessible for - // this rule. Each item in this list is combined using a logical OR. Empty matches all ports. - // As soon as a single item is present, only these ports are allowed. - Ports []NetworkPolicyPort `json:"ports,omitempty"` // From specifies the list of sources which should be able to send traffic to the // selected network interfaces. Fields are combined using a logical OR. Empty matches all sources. // As soon as a single item is present, only these peers are allowed. From []NetworkPolicyPeer `json:"from,omitempty"` + // Ports specifies the list of ports which should be made accessible for + // this rule. Each item in this list is combined using a logical OR. Empty matches all ports. + // As soon as a single item is present, only these ports are allowed. + Ports []NetworkPolicyPort `json:"ports,omitempty"` } // NetworkPolicyEgressRule describes a rule to regulate egress traffic with. @@ -98,10 +97,10 @@ const ( PolicyTypeEgress PolicyType = "Egress" ) -// +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient -// NetworkPolicy is the Schema for the networkpolicies API +// NetworkPolicy is the Schema for the networkpolicies API. type NetworkPolicy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/api/core/v1alpha1/networkpolicyrule_types.go b/api/core/v1alpha1/networkpolicyrule_types.go index 2cb9227a..e9969c0f 100644 --- a/api/core/v1alpha1/networkpolicyrule_types.go +++ b/api/core/v1alpha1/networkpolicyrule_types.go @@ -4,6 +4,7 @@ package v1alpha1 import ( + "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -19,10 +20,10 @@ type NetworkPolicyRule struct { // NetworkRef is the network the load balancer is assigned to. NetworkRef LocalUIDReference `json:"networkRef"` - // Targets are the targets of the network policy. - Targets []TargetNetworkInterface `json:"targets"` - + Targets []TargetNetworkInterface `json:"targets,omitempty"` + // Priority is an optional field that specifies the order in which the policy is applied. + Priority *int32 `json:"priority,omitempty"` // IngressRules are the ingress rules. IngressRules []Rule `json:"ingressRule,omitempty"` // EgressRules are the egress rules. @@ -32,7 +33,7 @@ type NetworkPolicyRule struct { // TargetNetworkInterface is the target of the network policy. type TargetNetworkInterface struct { // IP is the IP address of the target network interface. - IP IPAdd `json:"ip"` + IP net.IP `json:"ip"` // TargetRef is the target providing the destination. TargetRef *NetworkPolicyTargetRef `json:"targetRef,omitempty"` } @@ -58,7 +59,7 @@ type ObjectIP struct { // If unset but Prefix is set, this can be inferred. IPFamily corev1.IPFamily `json:"ipFamily,omitempty"` // Prefix is the prefix of the IP. - Prefix *IPPrefix `json:"prefix,omitempty"` + Prefix net.IPPrefix `json:"prefix,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index c41ce568..f792a536 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -173,16 +173,6 @@ func (in *IP) DeepCopyObject() runtime.Object { return nil } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAdd. -func (in *IPAdd) DeepCopy() *IPAdd { - if in == nil { - return nil - } - out := new(IPAdd) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAddress) DeepCopyInto(out *IPAddress) { *out = *in @@ -350,16 +340,6 @@ func (in *IPList) DeepCopyObject() runtime.Object { return nil } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPrefix. -func (in *IPPrefix) DeepCopy() *IPPrefix { - if in == nil { - return nil - } - out := new(IPPrefix) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPSpec) DeepCopyInto(out *IPSpec) { *out = *in @@ -1642,16 +1622,16 @@ func (in *NetworkPolicyEgressRule) DeepCopy() *NetworkPolicyEgressRule { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicyIngressRule) DeepCopyInto(out *NetworkPolicyIngressRule) { *out = *in - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]NetworkPolicyPort, len(*in)) + if in.From != nil { + in, out := &in.From, &out.From + *out = make([]NetworkPolicyPeer, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.From != nil { - in, out := &in.From, &out.From - *out = make([]NetworkPolicyPeer, len(*in)) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]NetworkPolicyPort, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -1767,6 +1747,11 @@ func (in *NetworkPolicyRule) DeepCopyInto(out *NetworkPolicyRule) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int32) + **out = **in + } if in.IngressRules != nil { in, out := &in.IngressRules, &out.IngressRules *out = make([]Rule, len(*in)) @@ -2116,10 +2101,7 @@ func (in *NodeStatus) DeepCopy() *NodeStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectIP) DeepCopyInto(out *ObjectIP) { *out = *in - if in.Prefix != nil { - in, out := &in.Prefix, &out.Prefix - *out = (*in).DeepCopy() - } + in.Prefix.DeepCopyInto(&out.Prefix) return } diff --git a/apinetlet/client/client.go b/apinetlet/client/client.go index 92203f5f..50d437bf 100644 --- a/apinetlet/client/client.go +++ b/apinetlet/client/client.go @@ -3,6 +3,38 @@ package client +import ( + "context" + + apinetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + const ( NetworkInterfaceProviderIDField = ".status.providerID" + NetworkPolicyNetworkNameField = "networkpolicy-network-name" ) + +func SetupNetworkPolicyNetworkNameFieldIndexer(ctx context.Context, indexer client.FieldIndexer) error { + return indexer.IndexField(ctx, &apinetv1alpha1.NetworkPolicy{}, NetworkPolicyNetworkNameField, func(obj client.Object) []string { + networkPolicy := obj.(*apinetv1alpha1.NetworkPolicy) + return []string{networkPolicy.Spec.NetworkRef.Name} + }) +} + +type Object[O any] interface { + client.Object + *O +} + +func ReconcileRequestsFromObjectStructSlice[O Object[OStruct], S ~[]OStruct, OStruct any](objs S) []reconcile.Request { + res := make([]reconcile.Request, len(objs)) + for i := range objs { + obj := O(&objs[i]) + res[i] = reconcile.Request{ + NamespacedName: client.ObjectKeyFromObject(obj), + } + } + return res +} diff --git a/apinetlet/controllers/controllers_suite_test.go b/apinetlet/controllers/controllers_suite_test.go index 8f9f13a6..72f44a48 100644 --- a/apinetlet/controllers/controllers_suite_test.go +++ b/apinetlet/controllers/controllers_suite_test.go @@ -11,6 +11,9 @@ import ( "github.com/ironcore-dev/controller-utils/buildutils" "github.com/ironcore-dev/controller-utils/modutils" + apinetletclient "github.com/ironcore-dev/ironcore-net/apinetlet/client" + apinetclient "github.com/ironcore-dev/ironcore-net/internal/client" + apinetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" "github.com/ironcore-dev/ironcore-net/client-go/ironcorenet" ipamv1alpha1 "github.com/ironcore-dev/ironcore/api/ipam/v1alpha1" @@ -138,6 +141,9 @@ func SetupTest(apiNetNamespace *corev1.Namespace) *corev1.Namespace { }) Expect(err).ToNot(HaveOccurred()) + Expect(apinetletclient.SetupNetworkPolicyNetworkNameFieldIndexer(ctx, k8sManager.GetFieldIndexer())).To(Succeed()) + Expect(apinetclient.SetupNetworkInterfaceNetworkNameFieldIndexer(ctx, k8sManager.GetFieldIndexer())).To(Succeed()) + apiNetInterface := ironcorenet.NewForConfigOrDie(cfg) // register reconciler here @@ -174,12 +180,20 @@ func SetupTest(apiNetNamespace *corev1.Namespace) *corev1.Namespace { APINetNamespace: apiNetNamespace.Name, }).SetupWithManager(k8sManager, k8sManager.GetCache())).To(Succeed()) + Expect((&NetworkPolicyReconciler{ + Client: k8sManager.GetClient(), + APINetClient: k8sManager.GetClient(), + APINetInterface: apiNetInterface, + APINetNamespace: apiNetNamespace.Name, + }).SetupWithManager(k8sManager, k8sManager.GetCache())).To(Succeed()) + mgrCtx, cancel := context.WithCancel(context.Background()) DeferCleanup(cancel) go func() { defer GinkgoRecover() Expect(k8sManager.Start(mgrCtx)).To(Succeed(), "failed to start manager") }() + }) return apiNetNamespace diff --git a/apinetlet/controllers/conversion.go b/apinetlet/controllers/conversion.go index c8663279..c3becfbe 100644 --- a/apinetlet/controllers/conversion.go +++ b/apinetlet/controllers/conversion.go @@ -9,9 +9,13 @@ import ( apinetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" apinetv1alpha1ac "github.com/ironcore-dev/ironcore-net/client-go/applyconfigurations/core/v1alpha1" + apinetmetav1ac "github.com/ironcore-dev/ironcore-net/client-go/applyconfigurations/meta/v1" + commonv1alpha1 "github.com/ironcore-dev/ironcore/api/common/v1alpha1" + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" networkingv1alpha1 "github.com/ironcore-dev/ironcore/api/networking/v1alpha1" utilslices "github.com/ironcore-dev/ironcore/utils/slices" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func ipToAPINetIP(ip commonv1alpha1.IP) net.IP { @@ -69,3 +73,85 @@ func apiNetNetworkInterfaceStateToNetworkInterfaceState(state apinetv1alpha1.Net return networkingv1alpha1.NetworkInterfaceStatePending } } + +func networkPolicyTypesToAPINetNetworkPolicyTypes(policyTypes []networkingv1alpha1.PolicyType) ([]apinetv1alpha1.PolicyType, error) { + var apiNetPolicyTypes []apinetv1alpha1.PolicyType + for _, policyType := range policyTypes { + switch policyType { + case networkingv1alpha1.PolicyTypeIngress: + apiNetPolicyTypes = append(apiNetPolicyTypes, apinetv1alpha1.PolicyTypeIngress) + case networkingv1alpha1.PolicyTypeEgress: + apiNetPolicyTypes = append(apiNetPolicyTypes, apinetv1alpha1.PolicyTypeEgress) + default: + return nil, fmt.Errorf("invalid policy type: %s", policyType) + } + } + return apiNetPolicyTypes, nil +} + +func translatePeers(peers []networkingv1alpha1.NetworkPolicyPeer) []apinetv1alpha1ac.NetworkPolicyPeerApplyConfiguration { + var apiNetPeers []apinetv1alpha1ac.NetworkPolicyPeerApplyConfiguration + for _, peer := range peers { + apiNetPeer := apinetv1alpha1ac.NetworkPolicyPeer(). + WithObjectSelector(translateObjectSelector(peer.ObjectSelector)). + WithIPBlock(translateIPBlock(peer.IPBlock)) + apiNetPeers = append(apiNetPeers, *apiNetPeer) + } + return apiNetPeers +} + +func translateIPBlock(ipBlock *networkingv1alpha1.IPBlock) *apinetv1alpha1ac.IPBlockApplyConfiguration { + if ipBlock == nil { + return nil + } + + var except []net.IPPrefix + for _, prefix := range ipBlock.Except { + except = append(except, net.IPPrefix(prefix)) + } + + return apinetv1alpha1ac.IPBlock(). + WithCIDR(net.IPPrefix(ipBlock.CIDR)). + WithExcept(except...) +} + +func translateObjectSelector(objSel *corev1alpha1.ObjectSelector) *apinetv1alpha1ac.ObjectSelectorApplyConfiguration { + if objSel == nil { + return nil + } + + return apinetv1alpha1ac.ObjectSelector(). + WithKind(objSel.Kind). + WithMatchLabels(objSel.MatchLabels). + WithMatchExpressions(translateLabelSelectorRequirements(objSel.MatchExpressions)...) +} + +func translateLabelSelectorRequirements(reqs []metav1.LabelSelectorRequirement) []*apinetmetav1ac.LabelSelectorRequirementApplyConfiguration { + var translated []*apinetmetav1ac.LabelSelectorRequirementApplyConfiguration + for _, req := range reqs { + translated = append(translated, apinetmetav1ac.LabelSelectorRequirement(). + WithKey(req.Key). + WithOperator(req.Operator). + WithValues(req.Values...)) + } + return translated +} + +func translateLabelSelector(labelSelector metav1.LabelSelector) *apinetmetav1ac.LabelSelectorApplyConfiguration { + return apinetmetav1ac.LabelSelector(). + WithMatchLabels(labelSelector.MatchLabels). + WithMatchExpressions(translateLabelSelectorRequirements(labelSelector.MatchExpressions)...) +} + +func translatePorts(ports []networkingv1alpha1.NetworkPolicyPort) []apinetv1alpha1ac.NetworkPolicyPortApplyConfiguration { + var apiNetPorts []apinetv1alpha1ac.NetworkPolicyPortApplyConfiguration + for _, port := range ports { + apiNetPort := apinetv1alpha1ac.NetworkPolicyPortApplyConfiguration{ + Protocol: port.Protocol, + Port: &port.Port, + EndPort: port.EndPort, + } + apiNetPorts = append(apiNetPorts, apiNetPort) + } + return apiNetPorts +} diff --git a/apinetlet/controllers/helper.go b/apinetlet/controllers/helper.go index d8c5fc0e..0a3fb77e 100644 --- a/apinetlet/controllers/helper.go +++ b/apinetlet/controllers/helper.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + apinetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" ipamv1alpha1 "github.com/ironcore-dev/ironcore/api/ipam/v1alpha1" networkingv1alpha1 "github.com/ironcore-dev/ironcore/api/networking/v1alpha1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -27,6 +28,17 @@ func getAPINetNetworkName(ctx context.Context, c client.Client, networkKey clien return string(network.UID), nil } +func getApiNetNetwork(ctx context.Context, c client.Client, apiNetNetworkKey client.ObjectKey) (*apinetv1alpha1.Network, error) { + network := &apinetv1alpha1.Network{} + if err := c.Get(ctx, apiNetNetworkKey, network); err != nil { + if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("error getting apiNetNetwork %s: %w", apiNetNetworkKey.Name, err) + } + return nil, nil + } + return network, nil +} + func isPrefixAllocated(prefix *ipamv1alpha1.Prefix) bool { return prefix.Status.Phase == ipamv1alpha1.PrefixPhaseAllocated } diff --git a/apinetlet/controllers/networkinterface_controller.go b/apinetlet/controllers/networkinterface_controller.go index 37a93df1..409e91e2 100644 --- a/apinetlet/controllers/networkinterface_controller.go +++ b/apinetlet/controllers/networkinterface_controller.go @@ -222,7 +222,18 @@ func (s *apiNetNetworkInterfaceClaimStrategy) ClaimState(claimer client.Object, func (s *apiNetNetworkInterfaceClaimStrategy) Adopt(ctx context.Context, claimer client.Object, obj client.Object) error { apiNetNic := obj.(*apinetv1alpha1.NetworkInterface) base := apiNetNic.DeepCopy() - metautils.SetLabels(apiNetNic, apinetletclient.SourceLabels(s.Scheme(), s.RESTMapper(), claimer)) + combinedLabels := make(map[string]string) + if claimerLabels := claimer.GetLabels(); claimerLabels != nil { + for key, value := range claimerLabels { + combinedLabels[key] = value + } + } + if sourceLabels := apinetletclient.SourceLabels(s.Scheme(), s.RESTMapper(), claimer); sourceLabels != nil { + for key, value := range sourceLabels { + combinedLabels[key] = value + } + } + apiNetNic.SetLabels(combinedLabels) return s.Patch(ctx, apiNetNic, client.StrategicMergeFrom(base)) } diff --git a/apinetlet/controllers/networkinterface_controller_test.go b/apinetlet/controllers/networkinterface_controller_test.go index 41c28058..f5a237ea 100644 --- a/apinetlet/controllers/networkinterface_controller_test.go +++ b/apinetlet/controllers/networkinterface_controller_test.go @@ -64,6 +64,9 @@ var _ = Describe("NetworkInterfaceController", func() { ObjectMeta: metav1.ObjectMeta{ Namespace: ns.Name, GenerateName: "nic-", + Labels: map[string]string{ + "app": "test", + }, }, Spec: networkingv1alpha1.NetworkInterfaceSpec{ ProviderID: provider.GetNetworkInterfaceID(apiNetNs.Name, apiNetNic.Name, "node", apiNetNic.UID), @@ -81,6 +84,7 @@ var _ = Describe("NetworkInterfaceController", func() { By("waiting for the APINet network interface to be claimed") Eventually(Object(apiNetNic)).Should(SatisfyAll( + HaveField("Labels", HaveKeyWithValue("app", "test")), HaveField("Spec.PublicIPs", ConsistOf(HaveField("IP", net.IP{Addr: publicIP.Addr}))), WithTransform(func(apiNetNic *apinetv1alpha1.NetworkInterface) *apinetletclient.SourceObjectData { return apinetletclient.SourceObjectDataFromObject( diff --git a/apinetlet/controllers/networkpolicy_controller.go b/apinetlet/controllers/networkpolicy_controller.go new file mode 100644 index 00000000..10583692 --- /dev/null +++ b/apinetlet/controllers/networkpolicy_controller.go @@ -0,0 +1,595 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package controllers + +import ( + "context" + "fmt" + "net/netip" + + "github.com/go-logr/logr" + "github.com/ironcore-dev/controller-utils/clientutils" + apinetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" + apinetletclient "github.com/ironcore-dev/ironcore-net/apinetlet/client" + apinetlethandler "github.com/ironcore-dev/ironcore-net/apinetlet/handler" + apinetv1alpha1ac "github.com/ironcore-dev/ironcore-net/client-go/applyconfigurations/core/v1alpha1" + "github.com/ironcore-dev/ironcore-net/client-go/ironcorenet" + apinetclient "github.com/ironcore-dev/ironcore-net/internal/client" + networkingv1alpha1 "github.com/ironcore-dev/ironcore/api/networking/v1alpha1" + "github.com/ironcore-dev/ironcore/utils/predicates" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +const ( + networkPolicyFinalizer = "apinet.ironcore.dev/networkpolicy" +) + +var ( + networkPolicyFieldOwner = client.FieldOwner(networkingv1alpha1.Resource("networkpolicies").String()) +) + +type NetworkPolicyReconciler struct { + client.Client + APINetClient client.Client + APINetInterface ironcorenet.Interface + + APINetNamespace string + + WatchFilterValue string +} + +//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=networking.ironcore.dev,resources=networkpolicies,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=networking.ironcore.dev,resources=networkpolicies/finalizers,verbs=update;patch + +//+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkinterfaces,verbs=get;list;watch +//+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networks,verbs=get;list;watch + +//+cluster=apinet:kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+cluster=apinet:kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkpolicyrules,verbs=get;list;watch;create;update;patch;delete;deletecollection + +func (r *NetworkPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + networkPolicy := &networkingv1alpha1.NetworkPolicy{} + if err := r.Get(ctx, req.NamespacedName, networkPolicy); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("error getting network policy %s: %w", req.NamespacedName, err) + } + + return r.deleteGone(ctx, log, req.NamespacedName) + } + + return r.reconcileExists(ctx, log, networkPolicy) +} + +func (r *NetworkPolicyReconciler) deleteGone(ctx context.Context, log logr.Logger, networkPolicyKey client.ObjectKey) (ctrl.Result, error) { + log.V(1).Info("Delete gone") + + log.V(1).Info("Deleting any matching apinet network policies") + if err := r.APINetClient.DeleteAllOf(ctx, &apinetv1alpha1.NetworkPolicy{}, + client.InNamespace(r.APINetNamespace), + apinetletclient.MatchingSourceKeyLabels(r.Scheme(), r.RESTMapper(), networkPolicyKey, &networkingv1alpha1.NetworkPolicy{}), + ); err != nil { + return ctrl.Result{}, fmt.Errorf("error deleting apinet network policies: %w", err) + } + + log.V(1).Info("Issued delete for any leftover APINet network policy") + return ctrl.Result{}, nil +} + +func (r *NetworkPolicyReconciler) reconcileExists(ctx context.Context, log logr.Logger, networkPolicy *networkingv1alpha1.NetworkPolicy) (ctrl.Result, error) { + log = log.WithValues("UID", networkPolicy.UID) + if !networkPolicy.DeletionTimestamp.IsZero() { + return r.delete(ctx, log, networkPolicy) + } + return r.reconcile(ctx, log, networkPolicy) +} + +func (r *NetworkPolicyReconciler) delete(ctx context.Context, log logr.Logger, networkPolicy *networkingv1alpha1.NetworkPolicy) (ctrl.Result, error) { + log.V(1).Info("Delete") + + if !controllerutil.ContainsFinalizer(networkPolicy, networkPolicyFinalizer) { + log.V(1).Info("No finalizer present, nothing to do") + return ctrl.Result{}, nil + } + + apiNetNetworkPolicy := &apinetv1alpha1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.APINetNamespace, + Name: string(networkPolicy.UID), + }, + } + if err := r.APINetClient.Delete(ctx, apiNetNetworkPolicy); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("error deleting apinet network policy: %w", err) + } + + log.V(1).Info("APINet network policy is gone, removing finalizer") + if err := clientutils.PatchRemoveFinalizer(ctx, r.Client, networkPolicy, networkPolicyFinalizer); err != nil { + return ctrl.Result{}, fmt.Errorf("error removing finalizer: %w", err) + } + log.V(1).Info("Deleted") + return ctrl.Result{}, nil + } + + log.V(1).Info("APINet network policy is not yet gone, requeueing") + return ctrl.Result{Requeue: true}, nil +} + +func (r *NetworkPolicyReconciler) reconcile(ctx context.Context, log logr.Logger, networkPolicy *networkingv1alpha1.NetworkPolicy) (ctrl.Result, error) { + log.V(1).Info("Reconcile") + + log.V(1).Info("Ensuring finalizer") + modified, err := clientutils.PatchEnsureFinalizer(ctx, r.Client, networkPolicy, networkPolicyFinalizer) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error ensuring finalizer: %w", err) + } + if modified { + log.V(1).Info("Added finalizer, requeueing") + return ctrl.Result{Requeue: true}, nil + } + + networkKey := client.ObjectKey{Namespace: networkPolicy.Namespace, Name: networkPolicy.Spec.NetworkRef.Name} + apiNetNetworkName, err := getAPINetNetworkName(ctx, r.Client, networkKey) + if err != nil { + return ctrl.Result{}, err + } + if apiNetNetworkName == "" { + log.V(1).Info("APINet network is not ready") + return ctrl.Result{}, nil + } + + apiNetNetworkKey := client.ObjectKey{Namespace: r.APINetNamespace, Name: apiNetNetworkName} + apiNetNetwork, err := getApiNetNetwork(ctx, r.APINetClient, apiNetNetworkKey) + if err != nil { + return ctrl.Result{}, err + } + + log.V(1).Info("Applying APINet network policy") + apiNetNetworkPolicy, err := r.applyAPINetNetworkPolicy(ctx, networkPolicy, apiNetNetworkName) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error applying apinet network policy: %w", err) + } + + log.V(1).Info("Finding APINet network interface targets") + targets, err := r.findTargets(ctx, apiNetNetworkPolicy) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error finding targets: %w", err) + } + + log.V(1).Info("Parsing ingress rules") + ingressRules, err := r.parseIngressRules(ctx, apiNetNetworkPolicy) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error parsing ingress rules: %w", err) + } + + log.V(1).Info("Parsing egress rules") + egressRules, err := r.parseEgressRules(ctx, apiNetNetworkPolicy) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error parsing egress rules: %w", err) + } + + log.V(1).Info("Applying APINet network policy rule", "targets", targets, "Network", klog.KObj(apiNetNetwork)) + if err := r.applyNetworkPolicyRule(ctx, networkPolicy, apiNetNetworkPolicy, targets, apiNetNetwork, ingressRules, egressRules); err != nil { + return ctrl.Result{}, fmt.Errorf("error applying apinet network policy rule: %w", err) + } + + log.V(1).Info("Reconciled") + return ctrl.Result{}, nil +} + +func (r *NetworkPolicyReconciler) findTargets(ctx context.Context, apiNetNetworkPolicy *apinetv1alpha1.NetworkPolicy) ([]apinetv1alpha1.TargetNetworkInterface, error) { + sel, err := metav1.LabelSelectorAsSelector(&apiNetNetworkPolicy.Spec.NetworkInterfaceSelector) + if err != nil { + return nil, err + } + + apiNetNicList := &apinetv1alpha1.NetworkInterfaceList{} + if err := r.APINetClient.List(ctx, apiNetNicList, + client.InNamespace(r.APINetNamespace), + client.MatchingLabelsSelector{Selector: sel}, + client.MatchingFields{apinetclient.NetworkInterfaceSpecNetworkRefNameField: apiNetNetworkPolicy.Spec.NetworkRef.Name}, + ); err != nil { + return nil, fmt.Errorf("error listing apinet network interfaces: %w", err) + } + + // Make slice non-nil so omitempty does not file. + targets := make([]apinetv1alpha1.TargetNetworkInterface, 0) + for _, apiNetNic := range apiNetNicList.Items { + if apiNetNic.Status.State != apinetv1alpha1.NetworkInterfaceStateReady { + continue + } + + for _, ip := range apiNetNic.Spec.IPs { + targets = append(targets, apinetv1alpha1.TargetNetworkInterface{ + IP: ip, + TargetRef: &apinetv1alpha1.NetworkPolicyTargetRef{ + UID: apiNetNic.UID, + Name: apiNetNic.Name, + }, + }) + } + } + + return targets, nil +} + +func (r *NetworkPolicyReconciler) parseIngressRules(ctx context.Context, np *apinetv1alpha1.NetworkPolicy) ([]apinetv1alpha1.Rule, error) { + var rules []apinetv1alpha1.Rule + + for _, ingress := range np.Spec.Ingress { + rule := apinetv1alpha1.Rule{} + for _, port := range ingress.Ports { + rule.NetworkPolicyPorts = append(rule.NetworkPolicyPorts, apinetv1alpha1.NetworkPolicyPort{ + Protocol: port.Protocol, + Port: port.Port, + EndPort: port.EndPort, + }) + } + + for _, from := range ingress.From { + if from.IPBlock != nil { + rule.CIDRBlock = append(rule.CIDRBlock, *from.IPBlock) + } + + if from.ObjectSelector != nil { + ips, err := r.processObjectSelector(ctx, np, from.ObjectSelector) + if err != nil { + return nil, err + } + rule.ObjectIPs = append(rule.ObjectIPs, ips...) + } + } + rules = append(rules, rule) + } + + return rules, nil +} + +func (r *NetworkPolicyReconciler) parseEgressRules(ctx context.Context, np *apinetv1alpha1.NetworkPolicy) ([]apinetv1alpha1.Rule, error) { + var rules []apinetv1alpha1.Rule + + for _, egress := range np.Spec.Egress { + rule := apinetv1alpha1.Rule{} + for _, port := range egress.Ports { + rule.NetworkPolicyPorts = append(rule.NetworkPolicyPorts, apinetv1alpha1.NetworkPolicyPort{ + Protocol: port.Protocol, + Port: port.Port, + EndPort: port.EndPort, + }) + } + + for _, to := range egress.To { + if to.IPBlock != nil { + rule.CIDRBlock = append(rule.CIDRBlock, *to.IPBlock) + } + + if to.ObjectSelector != nil { + ips, err := r.processObjectSelector(ctx, np, to.ObjectSelector) + if err != nil { + return nil, err + } + rule.ObjectIPs = append(rule.ObjectIPs, ips...) + } + } + rules = append(rules, rule) + } + + return rules, nil +} + +func (r *NetworkPolicyReconciler) processObjectSelector(ctx context.Context, np *apinetv1alpha1.NetworkPolicy, objectSelector *apinetv1alpha1.ObjectSelector) ([]apinetv1alpha1.ObjectIP, error) { + switch objectSelector.Kind { + case "NetworkInterface": + return r.fetchIPsFromNetworkInterfaces(ctx, np, objectSelector) + case "LoadBalancer": + return r.fetchIPsFromLoadBalancers(ctx, np, objectSelector) + //TODO: add more objects selector support if needed + default: + return nil, fmt.Errorf("unsupported object kind: %s", objectSelector.Kind) + } +} + +func (r *NetworkPolicyReconciler) fetchIPsFromNetworkInterfaces(ctx context.Context, np *apinetv1alpha1.NetworkPolicy, objectSelector *apinetv1alpha1.ObjectSelector) ([]apinetv1alpha1.ObjectIP, error) { + sel, err := metav1.LabelSelectorAsSelector(&objectSelector.LabelSelector) + if err != nil { + return nil, err + } + + nicList := &apinetv1alpha1.NetworkInterfaceList{} + if err := r.List(ctx, nicList, + client.InNamespace(np.Namespace), + client.MatchingLabelsSelector{Selector: sel}, + client.MatchingFields{apinetclient.NetworkInterfaceSpecNetworkRefNameField: np.Spec.NetworkRef.Name}, + ); err != nil { + return nil, fmt.Errorf("error listing apinet network interfaces: %w", err) + } + + var ips []apinetv1alpha1.ObjectIP + + for _, nic := range nicList.Items { + if nic.Status.State != apinetv1alpha1.NetworkInterfaceStateReady { + continue + } + + for _, ip := range nic.Spec.IPs { + ips = append(ips, apinetv1alpha1.ObjectIP{ + Prefix: net.IPPrefix{Prefix: netip.PrefixFrom(ip.Addr, 32)}, + IPFamily: corev1.IPv4Protocol, // TODO: later support for IPv6 + }) + } + } + + return ips, nil +} + +func (r *NetworkPolicyReconciler) fetchIPsFromLoadBalancers(ctx context.Context, np *apinetv1alpha1.NetworkPolicy, objectSelector *apinetv1alpha1.ObjectSelector) ([]apinetv1alpha1.ObjectIP, error) { + sel, err := metav1.LabelSelectorAsSelector(&objectSelector.LabelSelector) + if err != nil { + return nil, err + } + + //TODO: apinet load balancer need to inherit labels from ironcore load balancer + lbList := &apinetv1alpha1.LoadBalancerList{} + if err := r.List(ctx, lbList, + client.InNamespace(np.Namespace), + client.MatchingLabelsSelector{Selector: sel}, + ); err != nil { + return nil, fmt.Errorf("error listing apinet load balancers: %w", err) + } + + var ips []apinetv1alpha1.ObjectIP + + for _, lb := range lbList.Items { + //TODO: handle loadbalancer ports + for _, ip := range lb.Spec.IPs { + // TODO: handle LoadBalancerIP when only IPFamily is specified to allocate a random IP. + ips = append(ips, apinetv1alpha1.ObjectIP{ + Prefix: net.IPPrefix{Prefix: netip.PrefixFrom(ip.IP.Addr, 32)}, + IPFamily: corev1.IPv4Protocol, // TODO: later support for IPv6 + }) + } + } + + return ips, nil +} + +func (r *NetworkPolicyReconciler) applyNetworkPolicyRule( + ctx context.Context, + networkPolicy *networkingv1alpha1.NetworkPolicy, + apiNetNetworkPolicy *apinetv1alpha1.NetworkPolicy, + targets []apinetv1alpha1.TargetNetworkInterface, + network *apinetv1alpha1.Network, + ingressRules, egressRules []apinetv1alpha1.Rule, +) error { + networkPolicyRule := &apinetv1alpha1.NetworkPolicyRule{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkPolicyRule", + APIVersion: apinetv1alpha1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNetworkPolicy.Namespace, + Name: apiNetNetworkPolicy.Name, + Labels: apinetletclient.SourceLabels(r.Scheme(), r.RESTMapper(), networkPolicy), + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(apiNetNetworkPolicy, apinetv1alpha1.SchemeGroupVersion.WithKind("NetworkPolicy")), + }, + }, + NetworkRef: apinetv1alpha1.LocalUIDReference{ + Name: network.Name, + UID: network.UID, + }, + Priority: apiNetNetworkPolicy.Spec.Priority, + Targets: targets, + IngressRules: ingressRules, + EgressRules: egressRules, + } + _ = ctrl.SetControllerReference(apiNetNetworkPolicy, networkPolicyRule, r.Scheme()) + if err := r.Patch(ctx, networkPolicyRule, client.Apply, networkPolicyFieldOwner, client.ForceOwnership); err != nil { + return fmt.Errorf("error applying network policy rule: %w", err) + } + return nil +} + +func (r *NetworkPolicyReconciler) applyAPINetNetworkPolicy(ctx context.Context, networkPolicy *networkingv1alpha1.NetworkPolicy, apiNetNetworkName string) (*apinetv1alpha1.NetworkPolicy, error) { + var apiNetIngressRules []*apinetv1alpha1ac.NetworkPolicyIngressRuleApplyConfiguration + for _, ingressRule := range networkPolicy.Spec.Ingress { + apiNetIngressRule := &apinetv1alpha1ac.NetworkPolicyIngressRuleApplyConfiguration{ + From: translatePeers(ingressRule.From), + Ports: translatePorts(ingressRule.Ports), + } + apiNetIngressRules = append(apiNetIngressRules, apiNetIngressRule) + } + + var apiNetEgressRules []*apinetv1alpha1ac.NetworkPolicyEgressRuleApplyConfiguration + for _, egressRule := range networkPolicy.Spec.Egress { + apiNetEgressRule := &apinetv1alpha1ac.NetworkPolicyEgressRuleApplyConfiguration{ + To: translatePeers(egressRule.To), + Ports: translatePorts(egressRule.Ports), + } + apiNetEgressRules = append(apiNetEgressRules, apiNetEgressRule) + } + + apiNetNetworkPolicyTypes, err := networkPolicyTypesToAPINetNetworkPolicyTypes(networkPolicy.Spec.PolicyTypes) + if err != nil { + return nil, err + } + + nicSelector := translateLabelSelector(networkPolicy.Spec.NetworkInterfaceSelector) + + apiNetNetworkPolicyApplyCfg := + apinetv1alpha1ac.NetworkPolicy(string(networkPolicy.UID), r.APINetNamespace). + WithLabels(apinetletclient.SourceLabels(r.Scheme(), r.RESTMapper(), networkPolicy)). + WithSpec(apinetv1alpha1ac.NetworkPolicySpec(). + WithNetworkRef(corev1.LocalObjectReference{Name: apiNetNetworkName}). + WithNetworkInterfaceSelector(nicSelector). + WithPriority(1000). // set default value since networkingv1alpha1.NetworkPolicy does not have this field + WithIngress(apiNetIngressRules...). + WithEgress(apiNetEgressRules...). + WithPolicyTypes(apiNetNetworkPolicyTypes...), + ) + apiNetNetworkPolicy, err := r.APINetInterface.CoreV1alpha1(). + NetworkPolicies(r.APINetNamespace). + Apply(ctx, apiNetNetworkPolicyApplyCfg, metav1.ApplyOptions{FieldManager: string(fieldOwner), Force: true}) + if err != nil { + return nil, fmt.Errorf("error applying apinet network policy: %w", err) + } + return apiNetNetworkPolicy, nil +} + +func (r *NetworkPolicyReconciler) enqueueByNetwork() handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrl.Request { + log := ctrl.LoggerFrom(ctx) + apiNetNetwork := obj.(*apinetv1alpha1.Network) + + networkPolicyList := &apinetv1alpha1.NetworkPolicyList{} + if err := r.List(ctx, networkPolicyList, + client.InNamespace(apiNetNetwork.Namespace), + client.MatchingFields{apinetletclient.NetworkPolicyNetworkNameField: apiNetNetwork.Name}, + ); err != nil { + log.Error(err, "Error listing network policies for network") + return nil + } + + return apinetletclient.ReconcileRequestsFromObjectStructSlice[*apinetv1alpha1.NetworkPolicy](networkPolicyList.Items) + }) +} + +func (r *NetworkPolicyReconciler) enqueueByNetworkInterface() handler.EventHandler { + getEnqueueFunc := func(ctx context.Context, nic *apinetv1alpha1.NetworkInterface) func(nics []*apinetv1alpha1.NetworkInterface, queue workqueue.RateLimitingInterface) { + log := ctrl.LoggerFrom(ctx) + networkPolicyList := &apinetv1alpha1.NetworkPolicyList{} + if err := r.APINetClient.List(ctx, networkPolicyList, + client.InNamespace(nic.Namespace), + client.MatchingFields{apinetletclient.NetworkPolicyNetworkNameField: nic.Spec.NetworkRef.Name}, + ); err != nil { + log.Error(err, "Error listing apinent network policies for nic") + return nil + } + + return func(nics []*apinetv1alpha1.NetworkInterface, queue workqueue.RateLimitingInterface) { + for _, networkPolicy := range networkPolicyList.Items { + networkPolicyKey := client.ObjectKeyFromObject(&networkPolicy) + log := log.WithValues("networkPolicyKey", networkPolicyKey) + nicSelector := networkPolicy.Spec.NetworkInterfaceSelector + if len(nicSelector.MatchLabels) == 0 && len(nicSelector.MatchExpressions) == 0 { + return + } + + sel, err := metav1.LabelSelectorAsSelector(&nicSelector) + if err != nil { + log.Error(err, "Invalid network interface selector") + continue + } + + for _, nic := range nics { + if sel.Matches(labels.Set(nic.Labels)) { + queue.Add(ctrl.Request{NamespacedName: networkPolicyKey}) + break + } + } + } + } + } + + return handler.Funcs{ + CreateFunc: func(ctx context.Context, evt event.CreateEvent, queue workqueue.RateLimitingInterface) { + nic := evt.Object.(*apinetv1alpha1.NetworkInterface) + enqueueFunc := getEnqueueFunc(ctx, nic) + if enqueueFunc != nil { + enqueueFunc([]*apinetv1alpha1.NetworkInterface{nic}, queue) + } + }, + UpdateFunc: func(ctx context.Context, evt event.UpdateEvent, queue workqueue.RateLimitingInterface) { + newNic := evt.ObjectNew.(*apinetv1alpha1.NetworkInterface) + oldNic := evt.ObjectOld.(*apinetv1alpha1.NetworkInterface) + enqueueFunc := getEnqueueFunc(ctx, newNic) + if enqueueFunc != nil { + enqueueFunc([]*apinetv1alpha1.NetworkInterface{newNic, oldNic}, queue) + } + }, + DeleteFunc: func(ctx context.Context, evt event.DeleteEvent, queue workqueue.RateLimitingInterface) { + nic := evt.Object.(*apinetv1alpha1.NetworkInterface) + enqueueFunc := getEnqueueFunc(ctx, nic) + if enqueueFunc != nil { + enqueueFunc([]*apinetv1alpha1.NetworkInterface{nic}, queue) + } + }, + GenericFunc: func(ctx context.Context, evt event.GenericEvent, queue workqueue.RateLimitingInterface) { + nic := evt.Object.(*apinetv1alpha1.NetworkInterface) + enqueueFunc := getEnqueueFunc(ctx, nic) + if enqueueFunc != nil { + enqueueFunc([]*apinetv1alpha1.NetworkInterface{nic}, queue) + } + }, + } +} + +func (r *NetworkPolicyReconciler) networkInterfaceReadyPredicate() predicate.Predicate { + isNetworkInterfaceReady := func(nic *apinetv1alpha1.NetworkInterface) bool { + return nic.Status.State == apinetv1alpha1.NetworkInterfaceStateReady + } + return predicate.Funcs{ + CreateFunc: func(evt event.CreateEvent) bool { + nic := evt.Object.(*apinetv1alpha1.NetworkInterface) + return isNetworkInterfaceReady(nic) + }, + UpdateFunc: func(evt event.UpdateEvent) bool { + oldNic := evt.ObjectOld.(*apinetv1alpha1.NetworkInterface) + newNic := evt.ObjectNew.(*apinetv1alpha1.NetworkInterface) + return isNetworkInterfaceReady(oldNic) || isNetworkInterfaceReady(newNic) + }, + DeleteFunc: func(evt event.DeleteEvent) bool { + nic := evt.Object.(*apinetv1alpha1.NetworkInterface) + return isNetworkInterfaceReady(nic) + }, + GenericFunc: func(evt event.GenericEvent) bool { + nic := evt.Object.(*apinetv1alpha1.NetworkInterface) + return isNetworkInterfaceReady(nic) + }, + } +} + +func (r *NetworkPolicyReconciler) SetupWithManager(mgr ctrl.Manager, apiNetCache cache.Cache) error { + log := ctrl.Log.WithName("networkpolicy").WithName("setup") + + return ctrl.NewControllerManagedBy(mgr). + For( + &networkingv1alpha1.NetworkPolicy{}, + builder.WithPredicates( + predicates.ResourceHasFilterLabel(log, r.WatchFilterValue), + predicates.ResourceIsNotExternallyManaged(log), + ), + ). + WatchesRawSource( + source.Kind(apiNetCache, &apinetv1alpha1.NetworkPolicy{}), + apinetlethandler.EnqueueRequestForSource(r.Scheme(), r.RESTMapper(), &networkingv1alpha1.NetworkPolicy{}), + ). + Watches( + &apinetv1alpha1.Network{}, + r.enqueueByNetwork(), + ). + Watches( + &apinetv1alpha1.NetworkInterface{}, + r.enqueueByNetworkInterface(), + builder.WithPredicates(r.networkInterfaceReadyPredicate()), + ). + Complete(r) +} diff --git a/apinetlet/controllers/networkpolicy_controller_test.go b/apinetlet/controllers/networkpolicy_controller_test.go new file mode 100644 index 00000000..79951f7e --- /dev/null +++ b/apinetlet/controllers/networkpolicy_controller_test.go @@ -0,0 +1,657 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package controllers + +import ( + "net/netip" + + apinetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" + apinetletclient "github.com/ironcore-dev/ironcore-net/apinetlet/client" + commonv1alpha1 "github.com/ironcore-dev/ironcore/api/common/v1alpha1" + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" + networkingv1alpha1 "github.com/ironcore-dev/ironcore/api/networking/v1alpha1" + "github.com/ironcore-dev/ironcore/utils/generic" + . "github.com/ironcore-dev/ironcore/utils/testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" +) + +var _ = Describe("NetworkPolicyController", func() { + ns := SetupNamespace(&k8sClient) + apiNetNs := SetupNamespace(&k8sClient) + SetupTest(apiNetNs) + + network, apiNetNetwork := SetupNetwork(ns, apiNetNs) + + It("should manage and reconcile the APINet network policy and its rules without target apinet nic", func(ctx SpecContext) { + + By("creating an apinet nic for ingress") + ingressApiNetNic := &apinetv1alpha1.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + GenerateName: "apinet-nic-", + Labels: map[string]string{ + "rule": "ingress", + }, + }, + Spec: apinetv1alpha1.NetworkInterfaceSpec{ + NetworkRef: corev1.LocalObjectReference{Name: apiNetNetwork.Name}, + IPs: []net.IP{net.MustParseIP("192.168.178.50")}, + NodeRef: corev1.LocalObjectReference{Name: "test-node"}, + }, + } + Expect(k8sClient.Create(ctx, ingressApiNetNic)).To(Succeed()) + DeferCleanup(k8sClient.Delete, ingressApiNetNic) + + By("setting the ingress apinet nic to be ready") + Eventually(UpdateStatus(ingressApiNetNic, func() { + ingressApiNetNic.Status.State = apinetv1alpha1.NetworkInterfaceStateReady + })).Should(Succeed()) + + By("creating an apinet nic for egress") + egressApiNetNic := &apinetv1alpha1.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + GenerateName: "apinet-nic-", + Labels: map[string]string{ + "rule": "egress", + }, + }, + Spec: apinetv1alpha1.NetworkInterfaceSpec{ + NetworkRef: corev1.LocalObjectReference{Name: apiNetNetwork.Name}, + IPs: []net.IP{net.MustParseIP("192.168.178.60")}, + NodeRef: corev1.LocalObjectReference{Name: "test-node"}, + }, + } + Expect(k8sClient.Create(ctx, egressApiNetNic)).To(Succeed()) + DeferCleanup(k8sClient.Delete, egressApiNetNic) + + By("setting the egress apinet nic to be ready") + Eventually(UpdateStatus(egressApiNetNic, func() { + egressApiNetNic.Status.State = apinetv1alpha1.NetworkInterfaceStateReady + })).Should(Succeed()) + + By("creating an ironcore network policy") + np := &networkingv1alpha1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "network-policy-", + }, + Spec: networkingv1alpha1.NetworkPolicySpec{ + NetworkRef: corev1.LocalObjectReference{Name: network.Name}, + NetworkInterfaceSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "target", + }, + }, + PolicyTypes: []networkingv1alpha1.PolicyType{networkingv1alpha1.PolicyTypeIngress, networkingv1alpha1.PolicyTypeEgress}, + Ingress: []networkingv1alpha1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1alpha1.NetworkPolicyPort{ + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 80, + }, + { + Protocol: generic.Pointer(corev1.ProtocolUDP), + Port: 8080, + EndPort: generic.Pointer(int32(8090)), + }, + }, + From: []networkingv1alpha1.NetworkPolicyPeer{ + { + ObjectSelector: &corev1alpha1.ObjectSelector{ + Kind: "NetworkInterface", + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "ingress", + }, + }, + }, + }, + { + IPBlock: &networkingv1alpha1.IPBlock{ + CIDR: commonv1alpha1.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}, + Except: []commonv1alpha1.IPPrefix{ + {Prefix: netip.MustParsePrefix("192.168.1.1/32")}, + {Prefix: netip.MustParsePrefix("192.168.1.2/32")}}, + }, + }, + }, + }, + }, + Egress: []networkingv1alpha1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1alpha1.NetworkPolicyPort{ + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 443, + }, + }, + To: []networkingv1alpha1.NetworkPolicyPeer{ + { + ObjectSelector: &corev1alpha1.ObjectSelector{ + Kind: "NetworkInterface", + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "egress", + }, + }, + }, + }, + { + IPBlock: &networkingv1alpha1.IPBlock{ + CIDR: commonv1alpha1.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}, + }, + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, np)).To(Succeed()) + DeferCleanup(k8sClient.Delete, np) + + By("waiting for the APINet network policy to exist with correct specs") + apiNetNP := &apinetv1alpha1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + Name: string(np.UID), + }, + } + + Eventually(Object(apiNetNP)).Should(SatisfyAll( + HaveField("Labels", apinetletclient.SourceLabels(k8sClient.Scheme(), k8sClient.RESTMapper(), np)), + HaveField("Spec.NetworkRef", Equal(corev1.LocalObjectReference{Name: apiNetNetwork.Name})), + HaveField("Spec.NetworkInterfaceSelector.MatchLabels", Equal(map[string]string{"app": "target"})), + HaveField("Spec.PolicyTypes", ConsistOf(apinetv1alpha1.PolicyTypeIngress, apinetv1alpha1.PolicyTypeEgress)), + HaveField("Spec.Ingress", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Ports": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(80)), + }), + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolUDP)), + "Port": Equal(int32(8080)), + "EndPort": PointTo(Equal(int32(8090))), + }), + ), + "From": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "ObjectSelector": PointTo(MatchFields(IgnoreExtras, Fields{ + "Kind": Equal("NetworkInterface"), + "LabelSelector": Equal(metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "ingress", + }, + }), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "IPBlock": PointTo(MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}), + "Except": ConsistOf( + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.1/32")}), + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.2/32")}), + ), + })), + }), + ), + }), + )), + HaveField("Spec.Egress", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Ports": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(443)), + }), + ), + "To": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "ObjectSelector": PointTo(MatchFields(IgnoreExtras, Fields{ + "Kind": Equal("NetworkInterface"), + "LabelSelector": Equal(metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "egress", + }, + }), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "IPBlock": PointTo(MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}), + })), + }), + ), + }), + )), + )) + + By("waiting for the APINet network policy rule to exist with empty targets and other correct specs ") + networkPolicyRule := &apinetv1alpha1.NetworkPolicyRule{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNP.Namespace, + Name: apiNetNP.Name, + }, + } + + Eventually(Object(networkPolicyRule)).Should(SatisfyAll( + HaveField("NetworkRef", apinetv1alpha1.LocalUIDReference{ + Name: apiNetNetwork.Name, + UID: apiNetNetwork.UID, + }), + HaveField("Targets", BeEmpty()), + HaveField("IngressRules", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "NetworkPolicyPorts": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(80)), + "EndPort": BeNil(), + }), + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolUDP)), + "Port": Equal(int32(8080)), + "EndPort": PointTo(Equal(int32(8090))), + }), + ), + "CIDRBlock": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}), + "Except": ConsistOf( + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.1/32")}), + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.2/32")}), + )}), + ), + "ObjectIPs": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "IPFamily": Equal(corev1.IPv4Protocol), + "Prefix": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.178.50/32")})}), + ), + }), + )), + HaveField("EgressRules", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "NetworkPolicyPorts": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(443)), + "EndPort": BeNil(), + }), + ), + "CIDRBlock": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}), + "Except": BeEmpty()}), + ), + "ObjectIPs": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "IPFamily": Equal(corev1.IPv4Protocol), + "Prefix": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.178.60/32")})}), + ), + }), + )), + )) + }) + + It("should manage and reconcile the APINet network policy and its rules with available target apinet nic ", func(ctx SpecContext) { + + By("creating a target apinet nic") + targetApiNetNic1 := &apinetv1alpha1.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + GenerateName: "apinet-nic-", + Labels: map[string]string{ + "app": "target", + }, + }, + Spec: apinetv1alpha1.NetworkInterfaceSpec{ + NetworkRef: corev1.LocalObjectReference{Name: apiNetNetwork.Name}, + IPs: []net.IP{net.MustParseIP("192.168.178.1")}, + NodeRef: corev1.LocalObjectReference{Name: "test-node"}, + }, + } + Expect(k8sClient.Create(ctx, targetApiNetNic1)).To(Succeed()) + DeferCleanup(k8sClient.Delete, targetApiNetNic1) + + By("setting the target apinet nic to be ready") + Eventually(UpdateStatus(targetApiNetNic1, func() { + targetApiNetNic1.Status.State = apinetv1alpha1.NetworkInterfaceStateReady + })).Should(Succeed()) + + By("creating an apinet nic for ingress") + ingressApiNetNic := &apinetv1alpha1.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + GenerateName: "apinet-nic-", + Labels: map[string]string{ + "rule": "ingress", + }, + }, + Spec: apinetv1alpha1.NetworkInterfaceSpec{ + NetworkRef: corev1.LocalObjectReference{Name: apiNetNetwork.Name}, + IPs: []net.IP{net.MustParseIP("192.168.178.50")}, + NodeRef: corev1.LocalObjectReference{Name: "test-node"}, + }, + } + Expect(k8sClient.Create(ctx, ingressApiNetNic)).To(Succeed()) + DeferCleanup(k8sClient.Delete, ingressApiNetNic) + + By("setting the ingress apinet nic to be ready") + Eventually(UpdateStatus(ingressApiNetNic, func() { + ingressApiNetNic.Status.State = apinetv1alpha1.NetworkInterfaceStateReady + })).Should(Succeed()) + + By("creating an apinet nic for egress") + egressApiNetNic := &apinetv1alpha1.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + GenerateName: "apinet-nic-", + Labels: map[string]string{ + "rule": "egress", + }, + }, + Spec: apinetv1alpha1.NetworkInterfaceSpec{ + NetworkRef: corev1.LocalObjectReference{Name: apiNetNetwork.Name}, + IPs: []net.IP{net.MustParseIP("192.168.178.60")}, + NodeRef: corev1.LocalObjectReference{Name: "test-node"}, + }, + } + Expect(k8sClient.Create(ctx, egressApiNetNic)).To(Succeed()) + DeferCleanup(k8sClient.Delete, egressApiNetNic) + + By("setting the egress apinet nic to be ready") + Eventually(UpdateStatus(egressApiNetNic, func() { + egressApiNetNic.Status.State = apinetv1alpha1.NetworkInterfaceStateReady + })).Should(Succeed()) + + By("creating an ironcore network policy") + np := &networkingv1alpha1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "network-policy-", + }, + Spec: networkingv1alpha1.NetworkPolicySpec{ + NetworkRef: corev1.LocalObjectReference{Name: network.Name}, + NetworkInterfaceSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "target", + }, + }, + PolicyTypes: []networkingv1alpha1.PolicyType{networkingv1alpha1.PolicyTypeIngress, networkingv1alpha1.PolicyTypeEgress}, + Ingress: []networkingv1alpha1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1alpha1.NetworkPolicyPort{ + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 80, + }, + { + Protocol: generic.Pointer(corev1.ProtocolUDP), + Port: 8080, + EndPort: generic.Pointer(int32(8090)), + }, + }, + From: []networkingv1alpha1.NetworkPolicyPeer{ + { + ObjectSelector: &corev1alpha1.ObjectSelector{ + Kind: "NetworkInterface", + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "ingress", + }, + }, + }, + }, + { + IPBlock: &networkingv1alpha1.IPBlock{ + CIDR: commonv1alpha1.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}, + Except: []commonv1alpha1.IPPrefix{ + {Prefix: netip.MustParsePrefix("192.168.1.1/32")}, + {Prefix: netip.MustParsePrefix("192.168.1.2/32")}}, + }, + }, + }, + }, + }, + Egress: []networkingv1alpha1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1alpha1.NetworkPolicyPort{ + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 443, + }, + }, + To: []networkingv1alpha1.NetworkPolicyPeer{ + { + ObjectSelector: &corev1alpha1.ObjectSelector{ + Kind: "NetworkInterface", + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "egress", + }, + }, + }, + }, + { + IPBlock: &networkingv1alpha1.IPBlock{ + CIDR: commonv1alpha1.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}, + }, + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, np)).To(Succeed()) + DeferCleanup(k8sClient.Delete, np) + + By("waiting for the APINet network policy to exist with correct specs") + apiNetNP := &apinetv1alpha1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + Name: string(np.UID), + }, + } + + Eventually(Object(apiNetNP)).Should(SatisfyAll( + HaveField("Labels", apinetletclient.SourceLabels(k8sClient.Scheme(), k8sClient.RESTMapper(), np)), + HaveField("Spec.NetworkRef", Equal(corev1.LocalObjectReference{Name: apiNetNetwork.Name})), + HaveField("Spec.NetworkInterfaceSelector.MatchLabels", Equal(map[string]string{"app": "target"})), + HaveField("Spec.PolicyTypes", ConsistOf(apinetv1alpha1.PolicyTypeIngress, apinetv1alpha1.PolicyTypeEgress)), + HaveField("Spec.Ingress", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Ports": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(80)), + }), + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolUDP)), + "Port": Equal(int32(8080)), + "EndPort": PointTo(Equal(int32(8090))), + }), + ), + "From": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "ObjectSelector": PointTo(MatchFields(IgnoreExtras, Fields{ + "Kind": Equal("NetworkInterface"), + "LabelSelector": Equal(metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "ingress", + }, + }), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "IPBlock": PointTo(MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}), + "Except": ConsistOf( + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.1/32")}), + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.2/32")}), + ), + })), + }), + ), + }), + )), + HaveField("Spec.Egress", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Ports": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(443)), + }), + ), + "To": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "ObjectSelector": PointTo(MatchFields(IgnoreExtras, Fields{ + "Kind": Equal("NetworkInterface"), + "LabelSelector": Equal(metav1.LabelSelector{ + MatchLabels: map[string]string{ + "rule": "egress", + }, + }), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "IPBlock": PointTo(MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}), + })), + }), + ), + }), + )), + )) + + By("waiting for the APINet network policy rule to exist with correct specs") + networkPolicyRule := &apinetv1alpha1.NetworkPolicyRule{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNP.Namespace, + Name: apiNetNP.Name, + }, + } + + Eventually(Object(networkPolicyRule)).Should(SatisfyAll( + HaveField("NetworkRef", apinetv1alpha1.LocalUIDReference{ + Name: apiNetNetwork.Name, + UID: apiNetNetwork.UID, + }), + HaveField("Targets", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "IP": Equal(net.MustParseIP("192.168.178.1")), + "TargetRef": PointTo(MatchFields(IgnoreExtras, Fields{ + "UID": Equal(targetApiNetNic1.UID), + "Name": Equal(targetApiNetNic1.Name), + })), + }), + )), + HaveField("IngressRules", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "NetworkPolicyPorts": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(80)), + "EndPort": BeNil(), + }), + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolUDP)), + "Port": Equal(int32(8080)), + "EndPort": PointTo(Equal(int32(8090))), + }), + ), + "CIDRBlock": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}), + "Except": ConsistOf( + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.1/32")}), + Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.2/32")}), + )}), + ), + "ObjectIPs": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "IPFamily": Equal(corev1.IPv4Protocol), + "Prefix": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.178.50/32")})}), + ), + }), + )), + HaveField("EgressRules", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "NetworkPolicyPorts": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Protocol": PointTo(Equal(corev1.ProtocolTCP)), + "Port": Equal(int32(443)), + "EndPort": BeNil(), + }), + ), + "CIDRBlock": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "CIDR": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}), + "Except": BeEmpty()}), + ), + "ObjectIPs": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "IPFamily": Equal(corev1.IPv4Protocol), + "Prefix": Equal(net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.178.60/32")})}), + ), + }), + )), + )) + + By("creating another target apinet nic") + targetApiNetNic2 := &apinetv1alpha1.NetworkInterface{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: apiNetNs.Name, + GenerateName: "apinet-nic-", + Labels: map[string]string{ + "app": "target2", + }, + }, + Spec: apinetv1alpha1.NetworkInterfaceSpec{ + NetworkRef: corev1.LocalObjectReference{Name: apiNetNetwork.Name}, + IPs: []net.IP{net.MustParseIP("192.168.178.15")}, + NodeRef: corev1.LocalObjectReference{Name: "test-node"}, + }, + } + Expect(k8sClient.Create(ctx, targetApiNetNic2)).To(Succeed()) + DeferCleanup(k8sClient.Delete, targetApiNetNic2) + + By("setting the target apinet nic to be ready") + Eventually(UpdateStatus(targetApiNetNic2, func() { + targetApiNetNic2.Status.State = apinetv1alpha1.NetworkInterfaceStateReady + })).Should(Succeed()) + + By("updating the ironcore networkpolicy with new NetworkInterfaceSelector labels") + Eventually(Update(np, func() { + np.Spec.NetworkInterfaceSelector.MatchLabels = map[string]string{"app": "target2"} + })).Should(Succeed()) + + By("waiting for the APINet network policy rule to be updated with new target network interface") + Eventually(Object(networkPolicyRule)).Should(SatisfyAll( + HaveField("NetworkRef", apinetv1alpha1.LocalUIDReference{ + Name: apiNetNetwork.Name, + UID: apiNetNetwork.UID, + }), + HaveField("Targets", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "IP": Equal(net.MustParseIP("192.168.178.15")), + "TargetRef": PointTo(MatchFields(IgnoreExtras, Fields{ + "UID": Equal(targetApiNetNic2.UID), + "Name": Equal(targetApiNetNic2.Name), + })), + }), + )), + )) + + }) +}) diff --git a/client-go/applyconfigurations/core/v1alpha1/networkpolicyingressrule.go b/client-go/applyconfigurations/core/v1alpha1/networkpolicyingressrule.go index a374581e..b9f6aa2a 100644 --- a/client-go/applyconfigurations/core/v1alpha1/networkpolicyingressrule.go +++ b/client-go/applyconfigurations/core/v1alpha1/networkpolicyingressrule.go @@ -8,8 +8,8 @@ package v1alpha1 // NetworkPolicyIngressRuleApplyConfiguration represents an declarative configuration of the NetworkPolicyIngressRule type for use // with apply. type NetworkPolicyIngressRuleApplyConfiguration struct { - Ports []NetworkPolicyPortApplyConfiguration `json:"ports,omitempty"` From []NetworkPolicyPeerApplyConfiguration `json:"from,omitempty"` + Ports []NetworkPolicyPortApplyConfiguration `json:"ports,omitempty"` } // NetworkPolicyIngressRuleApplyConfiguration constructs an declarative configuration of the NetworkPolicyIngressRule type for use with @@ -18,28 +18,28 @@ func NetworkPolicyIngressRule() *NetworkPolicyIngressRuleApplyConfiguration { return &NetworkPolicyIngressRuleApplyConfiguration{} } -// WithPorts adds the given value to the Ports field in the declarative configuration +// WithFrom adds the given value to the From field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the Ports field. -func (b *NetworkPolicyIngressRuleApplyConfiguration) WithPorts(values ...*NetworkPolicyPortApplyConfiguration) *NetworkPolicyIngressRuleApplyConfiguration { +// If called multiple times, values provided by each call will be appended to the From field. +func (b *NetworkPolicyIngressRuleApplyConfiguration) WithFrom(values ...*NetworkPolicyPeerApplyConfiguration) *NetworkPolicyIngressRuleApplyConfiguration { for i := range values { if values[i] == nil { - panic("nil value passed to WithPorts") + panic("nil value passed to WithFrom") } - b.Ports = append(b.Ports, *values[i]) + b.From = append(b.From, *values[i]) } return b } -// WithFrom adds the given value to the From field in the declarative configuration +// WithPorts adds the given value to the Ports field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, values provided by each call will be appended to the From field. -func (b *NetworkPolicyIngressRuleApplyConfiguration) WithFrom(values ...*NetworkPolicyPeerApplyConfiguration) *NetworkPolicyIngressRuleApplyConfiguration { +// If called multiple times, values provided by each call will be appended to the Ports field. +func (b *NetworkPolicyIngressRuleApplyConfiguration) WithPorts(values ...*NetworkPolicyPortApplyConfiguration) *NetworkPolicyIngressRuleApplyConfiguration { for i := range values { if values[i] == nil { - panic("nil value passed to WithFrom") + panic("nil value passed to WithPorts") } - b.From = append(b.From, *values[i]) + b.Ports = append(b.Ports, *values[i]) } return b } diff --git a/client-go/applyconfigurations/core/v1alpha1/networkpolicyrule.go b/client-go/applyconfigurations/core/v1alpha1/networkpolicyrule.go index 95715d05..f63bd8c7 100644 --- a/client-go/applyconfigurations/core/v1alpha1/networkpolicyrule.go +++ b/client-go/applyconfigurations/core/v1alpha1/networkpolicyrule.go @@ -21,6 +21,7 @@ type NetworkPolicyRuleApplyConfiguration struct { *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` NetworkRef *LocalUIDReferenceApplyConfiguration `json:"networkRef,omitempty"` Targets []TargetNetworkInterfaceApplyConfiguration `json:"targets,omitempty"` + Priority *int32 `json:"priority,omitempty"` IngressRules []RuleApplyConfiguration `json:"ingressRule,omitempty"` EgressRules []RuleApplyConfiguration `json:"egressRule,omitempty"` } @@ -251,6 +252,14 @@ func (b *NetworkPolicyRuleApplyConfiguration) WithTargets(values ...*TargetNetwo return b } +// WithPriority sets the Priority field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Priority field is set to the value of the last call. +func (b *NetworkPolicyRuleApplyConfiguration) WithPriority(value int32) *NetworkPolicyRuleApplyConfiguration { + b.Priority = &value + return b +} + // WithIngressRules adds the given value to the IngressRules field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, values provided by each call will be appended to the IngressRules field. diff --git a/client-go/applyconfigurations/core/v1alpha1/objectip.go b/client-go/applyconfigurations/core/v1alpha1/objectip.go index 9244a586..c50070ff 100644 --- a/client-go/applyconfigurations/core/v1alpha1/objectip.go +++ b/client-go/applyconfigurations/core/v1alpha1/objectip.go @@ -6,15 +6,15 @@ package v1alpha1 import ( - v1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + net "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" v1 "k8s.io/api/core/v1" ) // ObjectIPApplyConfiguration represents an declarative configuration of the ObjectIP type for use // with apply. type ObjectIPApplyConfiguration struct { - IPFamily *v1.IPFamily `json:"ipFamily,omitempty"` - Prefix *v1alpha1.IPPrefix `json:"prefix,omitempty"` + IPFamily *v1.IPFamily `json:"ipFamily,omitempty"` + Prefix *net.IPPrefix `json:"prefix,omitempty"` } // ObjectIPApplyConfiguration constructs an declarative configuration of the ObjectIP type for use with @@ -34,7 +34,7 @@ func (b *ObjectIPApplyConfiguration) WithIPFamily(value v1.IPFamily) *ObjectIPAp // WithPrefix sets the Prefix field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Prefix field is set to the value of the last call. -func (b *ObjectIPApplyConfiguration) WithPrefix(value v1alpha1.IPPrefix) *ObjectIPApplyConfiguration { +func (b *ObjectIPApplyConfiguration) WithPrefix(value net.IPPrefix) *ObjectIPApplyConfiguration { b.Prefix = &value return b } diff --git a/client-go/applyconfigurations/core/v1alpha1/targetnetworkinterface.go b/client-go/applyconfigurations/core/v1alpha1/targetnetworkinterface.go index df66060a..9a82987e 100644 --- a/client-go/applyconfigurations/core/v1alpha1/targetnetworkinterface.go +++ b/client-go/applyconfigurations/core/v1alpha1/targetnetworkinterface.go @@ -6,13 +6,13 @@ package v1alpha1 import ( - v1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + net "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" ) // TargetNetworkInterfaceApplyConfiguration represents an declarative configuration of the TargetNetworkInterface type for use // with apply. type TargetNetworkInterfaceApplyConfiguration struct { - IP *v1alpha1.IPAdd `json:"ip,omitempty"` + IP *net.IP `json:"ip,omitempty"` TargetRef *NetworkPolicyTargetRefApplyConfiguration `json:"targetRef,omitempty"` } @@ -25,7 +25,7 @@ func TargetNetworkInterface() *TargetNetworkInterfaceApplyConfiguration { // WithIP sets the IP field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the IP field is set to the value of the last call. -func (b *TargetNetworkInterfaceApplyConfiguration) WithIP(value v1alpha1.IPAdd) *TargetNetworkInterfaceApplyConfiguration { +func (b *TargetNetworkInterfaceApplyConfiguration) WithIP(value net.IP) *TargetNetworkInterfaceApplyConfiguration { b.IP = &value return b } diff --git a/client-go/applyconfigurations/internal/internal.go b/client-go/applyconfigurations/internal/internal.go index ea7c6ca7..b1d24acb 100644 --- a/client-go/applyconfigurations/internal/internal.go +++ b/client-go/applyconfigurations/internal/internal.go @@ -93,18 +93,6 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPStatus default: {} -- name: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPAdd - map: - elementType: - scalar: untyped - list: - elementType: - namedType: __untyped_atomic_ - elementRelationship: atomic - map: - elementType: - namedType: __untyped_deduced_ - elementRelationship: separable - name: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPAddress map: fields: @@ -177,18 +165,6 @@ var schemaYAML = typed.YAMLObject(`types: - name: uid type: scalar: string -- name: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPPrefix - map: - elementType: - scalar: untyped - list: - elementType: - namedType: __untyped_atomic_ - elementRelationship: atomic - map: - elementType: - namedType: __untyped_deduced_ - elementRelationship: separable - name: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPSpec map: fields: @@ -911,6 +887,9 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.LocalUIDReference default: {} + - name: priority + type: + scalar: numeric - name: targets type: list: @@ -1078,7 +1057,7 @@ var schemaYAML = typed.YAMLObject(`types: scalar: string - name: prefix type: - namedType: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPPrefix + namedType: com.github.ironcore-dev.ironcore-net.apimachinery.api.net.IPPrefix - name: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.ObjectSelector map: fields: @@ -1138,8 +1117,7 @@ var schemaYAML = typed.YAMLObject(`types: fields: - name: ip type: - namedType: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.IPAdd - default: {} + namedType: com.github.ironcore-dev.ironcore-net.apimachinery.api.net.IP - name: targetRef type: namedType: com.github.ironcore-dev.ironcore-net.api.core.v1alpha1.NetworkPolicyTargetRef diff --git a/client-go/openapi/zz_generated.openapi.go b/client-go/openapi/zz_generated.openapi.go index a297e6c2..18b767f2 100644 --- a/client-go/openapi/zz_generated.openapi.go +++ b/client-go/openapi/zz_generated.openapi.go @@ -26,7 +26,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.DaemonSetSpec": schema_ironcore_net_api_core_v1alpha1_DaemonSetSpec(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.DaemonSetStatus": schema_ironcore_net_api_core_v1alpha1_DaemonSetStatus(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IP": schema_ironcore_net_api_core_v1alpha1_IP(ref), - "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPAdd": schema_ironcore_net_api_core_v1alpha1_IPAdd(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPAddress": schema_ironcore_net_api_core_v1alpha1_IPAddress(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPAddressClaimRef": schema_ironcore_net_api_core_v1alpha1_IPAddressClaimRef(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPAddressList": schema_ironcore_net_api_core_v1alpha1_IPAddressList(ref), @@ -34,7 +33,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPBlock": schema_ironcore_net_api_core_v1alpha1_IPBlock(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPClaimRef": schema_ironcore_net_api_core_v1alpha1_IPClaimRef(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPList": schema_ironcore_net_api_core_v1alpha1_IPList(ref), - "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPPrefix": schema_ironcore_net_api_core_v1alpha1_IPPrefix(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPSpec": schema_ironcore_net_api_core_v1alpha1_IPSpec(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPStatus": schema_ironcore_net_api_core_v1alpha1_IPStatus(ref), "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.Instance": schema_ironcore_net_api_core_v1alpha1_Instance(ref), @@ -601,17 +599,6 @@ func schema_ironcore_net_api_core_v1alpha1_IP(ref common.ReferenceCallback) comm } } -func schema_ironcore_net_api_core_v1alpha1_IPAdd(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "IPAdd is an IP address.", - Type: []string{"object"}, - }, - }, - } -} - func schema_ironcore_net_api_core_v1alpha1_IPAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -889,17 +876,6 @@ func schema_ironcore_net_api_core_v1alpha1_IPList(ref common.ReferenceCallback) } } -func schema_ironcore_net_api_core_v1alpha1_IPPrefix(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "IPPrefix represents a network prefix.", - Type: []string{"object"}, - }, - }, - } -} - func schema_ironcore_net_api_core_v1alpha1_IPSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2879,7 +2855,7 @@ func schema_ironcore_net_api_core_v1alpha1_NetworkPolicy(ref common.ReferenceCal return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NetworkPolicy is the Schema for the networkpolicies API", + Description: "NetworkPolicy is the Schema for the networkpolicies API.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -2966,29 +2942,29 @@ func schema_ironcore_net_api_core_v1alpha1_NetworkPolicyIngressRule(ref common.R Description: "NetworkPolicyIngressRule describes a rule to regulate ingress traffic with.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ports": { + "from": { SchemaProps: spec.SchemaProps{ - Description: "Ports specifies the list of ports which should be made accessible for this rule. Each item in this list is combined using a logical OR. Empty matches all ports. As soon as a single item is present, only these ports are allowed.", + Description: "From specifies the list of sources which should be able to send traffic to the selected network interfaces. Fields are combined using a logical OR. Empty matches all sources. As soon as a single item is present, only these peers are allowed.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.NetworkPolicyPort"), + Ref: ref("github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.NetworkPolicyPeer"), }, }, }, }, }, - "from": { + "ports": { SchemaProps: spec.SchemaProps{ - Description: "From specifies the list of sources which should be able to send traffic to the selected network interfaces. Fields are combined using a logical OR. Empty matches all sources. As soon as a single item is present, only these peers are allowed.", + Description: "Ports specifies the list of ports which should be made accessible for this rule. Each item in this list is combined using a logical OR. Empty matches all ports. As soon as a single item is present, only these ports are allowed.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.NetworkPolicyPeer"), + Ref: ref("github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.NetworkPolicyPort"), }, }, }, @@ -3161,6 +3137,13 @@ func schema_ironcore_net_api_core_v1alpha1_NetworkPolicyRule(ref common.Referenc }, }, }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "Priority is an optional field that specifies the order in which the policy is applied.", + Type: []string{"integer"}, + Format: "int32", + }, + }, "ingressRule": { SchemaProps: spec.SchemaProps{ Description: "IngressRules are the ingress rules.", @@ -3190,7 +3173,7 @@ func schema_ironcore_net_api_core_v1alpha1_NetworkPolicyRule(ref common.Referenc }, }, }, - Required: []string{"networkRef", "targets"}, + Required: []string{"networkRef"}, }, }, Dependencies: []string{ @@ -3251,8 +3234,7 @@ func schema_ironcore_net_api_core_v1alpha1_NetworkPolicySpec(ref common.Referenc return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NetworkPolicySpec defines the desired state of NetworkPolicy.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ "networkRef": { SchemaProps: spec.SchemaProps{ @@ -3655,14 +3637,14 @@ func schema_ironcore_net_api_core_v1alpha1_ObjectIP(ref common.ReferenceCallback "prefix": { SchemaProps: spec.SchemaProps{ Description: "Prefix is the prefix of the IP.", - Ref: ref("github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPPrefix"), + Ref: ref("github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IPPrefix"), }, }, }, }, }, Dependencies: []string{ - "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPPrefix"}, + "github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IPPrefix"}, } } @@ -3823,8 +3805,7 @@ func schema_ironcore_net_api_core_v1alpha1_TargetNetworkInterface(ref common.Ref "ip": { SchemaProps: spec.SchemaProps{ Description: "IP is the IP address of the target network interface.", - Default: map[string]interface{}{}, - Ref: ref("github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPAdd"), + Ref: ref("github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IP"), }, }, "targetRef": { @@ -3838,7 +3819,7 @@ func schema_ironcore_net_api_core_v1alpha1_TargetNetworkInterface(ref common.Ref }, }, Dependencies: []string{ - "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.IPAdd", "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.NetworkPolicyTargetRef"}, + "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1.NetworkPolicyTargetRef", "github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IP"}, } } diff --git a/cmd/apinetlet/main.go b/cmd/apinetlet/main.go index 77843c83..118e93eb 100644 --- a/cmd/apinetlet/main.go +++ b/cmd/apinetlet/main.go @@ -210,6 +210,17 @@ func main() { os.Exit(1) } + if err = (&controllers.NetworkPolicyReconciler{ + Client: mgr.GetClient(), + APINetClient: apiNetCluster.GetClient(), + APINetInterface: apiNetIface, + APINetNamespace: apiNetNamespace, + WatchFilterValue: watchFilterValue, + }).SetupWithManager(mgr, apiNetCluster.GetCache()); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NetworkPolicy") + os.Exit(1) + } + if err = (&controllers.VirtualIPReconciler{ Client: mgr.GetClient(), APINetClient: apiNetCluster.GetClient(), diff --git a/cmd/controller-manager/main.go b/cmd/controller-manager/main.go index c1af5a08..fa4813e0 100644 --- a/cmd/controller-manager/main.go +++ b/cmd/controller-manager/main.go @@ -9,7 +9,9 @@ import ( "github.com/ironcore-dev/controller-utils/configutils" ironcorenetv1alpha1 "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + apinetletclient "github.com/ironcore-dev/ironcore-net/apinetlet/client" apinetclient "github.com/ironcore-dev/ironcore-net/internal/client" + "github.com/ironcore-dev/ironcore-net/internal/controllers" ironcorenet "github.com/ironcore-dev/ironcore-net/internal/controllers/certificate/ironcore-net" "github.com/ironcore-dev/ironcore-net/internal/controllers/scheduler" @@ -117,6 +119,13 @@ func main() { os.Exit(1) } + if err = (&controllers.NetworkPolicyReconciler{ + Client: mgr.GetClient(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NetworkPolicy") + os.Exit(1) + } + if err = (&controllers.NATGatewayReconciler{ Client: mgr.GetClient(), EventRecorder: mgr.GetEventRecorderFor("natgateways"), @@ -172,6 +181,11 @@ func main() { os.Exit(1) } + if err := apinetletclient.SetupNetworkPolicyNetworkNameFieldIndexer(ctx, mgr.GetFieldIndexer()); err != nil { + setupLog.Error(err, "unable to setup field indexer", "field", apinetletclient.NetworkPolicyNetworkNameField) + os.Exit(1) + } + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) diff --git a/config/apinetlet/apinet-rbac/role.yaml b/config/apinetlet/apinet-rbac/role.yaml index 4e95c338..0ee5289c 100644 --- a/config/apinetlet/apinet-rbac/role.yaml +++ b/config/apinetlet/apinet-rbac/role.yaml @@ -106,6 +106,32 @@ rules: - patch - update - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicies + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicyrules + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch - apiGroups: - core.apinet.ironcore.dev resources: diff --git a/config/apinetlet/rbac/role.yaml b/config/apinetlet/rbac/role.yaml index 7fc2bd65..5882c7d2 100644 --- a/config/apinetlet/rbac/role.yaml +++ b/config/apinetlet/rbac/role.yaml @@ -22,6 +22,22 @@ rules: - patch - update - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkinterfaces + verbs: + - get + - list + - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networks + verbs: + - get + - list + - watch - apiGroups: - ipam.ironcore.dev resources: @@ -113,6 +129,23 @@ rules: - get - patch - update +- apiGroups: + - networking.ironcore.dev + resources: + - networkpolicies + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.ironcore.dev + resources: + - networkpolicies/finalizers + verbs: + - patch + - update - apiGroups: - networking.ironcore.dev resources: diff --git a/config/apiserver/rbac/apinetlet_role.yaml b/config/apiserver/rbac/apinetlet_role.yaml index ad04c8da..d3f202c7 100644 --- a/config/apiserver/rbac/apinetlet_role.yaml +++ b/config/apiserver/rbac/apinetlet_role.yaml @@ -106,6 +106,32 @@ rules: - patch - update - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicies + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicyrules + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch - apiGroups: - core.apinet.ironcore.dev resources: diff --git a/config/apiserver/rbac/metalnetlet_role.yaml b/config/apiserver/rbac/metalnetlet_role.yaml index dd7e35c5..ebb873b6 100644 --- a/config/apiserver/rbac/metalnetlet_role.yaml +++ b/config/apiserver/rbac/metalnetlet_role.yaml @@ -120,6 +120,22 @@ rules: - get - patch - update +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicies + verbs: + - get + - list + - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicyrules + verbs: + - get + - list + - watch - apiGroups: - core.apinet.ironcore.dev resources: diff --git a/config/controller/rbac/role.yaml b/config/controller/rbac/role.yaml index 307e29cf..8d7ebdb0 100644 --- a/config/controller/rbac/role.yaml +++ b/config/controller/rbac/role.yaml @@ -176,6 +176,26 @@ rules: - patch - update - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicies + verbs: + - get + - list + - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicyrules + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - core.apinet.ironcore.dev resources: diff --git a/config/metalnetlet/apinet-rbac/role.yaml b/config/metalnetlet/apinet-rbac/role.yaml index 53f4ec00..8d9dd3cb 100644 --- a/config/metalnetlet/apinet-rbac/role.yaml +++ b/config/metalnetlet/apinet-rbac/role.yaml @@ -120,6 +120,22 @@ rules: - get - patch - update +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicies + verbs: + - get + - list + - watch +- apiGroups: + - core.apinet.ironcore.dev + resources: + - networkpolicyrules + verbs: + - get + - list + - watch - apiGroups: - core.apinet.ironcore.dev resources: diff --git a/docs/api-reference/core.md b/docs/api-reference/core.md index 42525204..d922b7b5 100644 --- a/docs/api-reference/core.md +++ b/docs/api-reference/core.md @@ -34,6 +34,10 @@ Resource Types:
  • NetworkInterface
  • +NetworkPolicy +
  • +NetworkPolicyRule +
  • Node
  • DaemonSet @@ -1343,6 +1347,257 @@ NetworkInterfaceStatus +

    NetworkPolicy +

    +
    +

    NetworkPolicy is the Schema for the networkpolicies API.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +apiVersion
    +string
    + +core.apinet.ironcore.dev/v1alpha1 + +
    +kind
    +string +
    NetworkPolicy
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +NetworkPolicySpec + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +networkRef
    + + +Kubernetes core/v1.LocalObjectReference + + +
    +

    NetworkRef is the network to regulate using this policy.

    +
    +networkInterfaceSelector
    + + +Kubernetes meta/v1.LabelSelector + + +
    +

    NetworkInterfaceSelector selects the network interfaces that are subject to this policy.

    +
    +priority
    + +int32 + +
    +

    Priority is an optional field that specifies the order in which the policy is applied. +Policies with higher “order” are applied after those with lower +order. If the order is omitted, it may be considered to be “infinite” - i.e. the +policy will be applied last. Policies with identical order will be applied in +alphanumerical order based on the Policy “Name”.

    +
    +ingress
    + + +[]NetworkPolicyIngressRule + + +
    +

    Ingress specifies rules for ingress traffic.

    +
    +egress
    + + +[]NetworkPolicyEgressRule + + +
    +

    Egress specifies rules for egress traffic.

    +
    +policyTypes
    + + +[]PolicyType + + +
    +

    PolicyTypes specifies the types of policies this network policy contains.

    +
    +
    +

    NetworkPolicyRule +

    +
    +

    NetworkPolicyRule is the schema for the networkpolicyrules API.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +apiVersion
    +string
    + +core.apinet.ironcore.dev/v1alpha1 + +
    +kind
    +string +
    NetworkPolicyRule
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +networkRef
    + + +LocalUIDReference + + +
    +

    NetworkRef is the network the load balancer is assigned to.

    +
    +targets
    + + +[]TargetNetworkInterface + + +
    +

    Targets are the targets of the network policy.

    +
    +priority
    + +int32 + +
    +

    Priority is an optional field that specifies the order in which the policy is applied.

    +
    +ingressRule
    + + +[]Rule + + +
    +

    IngressRules are the ingress rules.

    +
    +egressRule
    + + +[]Rule + + +
    +

    EgressRules are the egress rules.

    +

    Node

    @@ -1638,6 +1893,51 @@ IPAddressClaimRef +

    IPBlock +

    +

    +(Appears on:NetworkPolicyPeer, Rule) +

    +
    +

    IPBlock specifies an ip block with optional exceptions.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +cidr
    + + +github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IPPrefix + + +
    +

    CIDR is a string representing the ip block.

    +
    +except
    + + +[]github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IPPrefix + + +
    +

    Except is a slice of CIDRs that should not be included within the specified CIDR. +Values will be rejected if they are outside CIDR.

    +

    IPClaimRef

    @@ -2557,12 +2857,13 @@ Kubernetes core/v1.LocalObjectReference -

    NATGatewayAutoscalerSpec +

    LocalUIDReference

    -(Appears on:NATGatewayAutoscaler) +(Appears on:NetworkPolicyRule)

    +

    LocalUIDReference is a reference to another entity including its UID

    @@ -2574,23 +2875,64 @@ Kubernetes core/v1.LocalObjectReference + + + +
    -natGatewayRef
    +name
    - -Kubernetes core/v1.LocalObjectReference - +string
    -

    NATGatewayRef points to the target NATGateway to scale.

    +

    Name is the name of the referenced entity.

    -minPublicIPs
    +uid
    -int32 - + +k8s.io/apimachinery/pkg/types.UID + + +
    +

    UID is the UID of the referenced entity.

    +
    +

    NATGatewayAutoscalerSpec +

    +

    +(Appears on:NATGatewayAutoscaler) +

    +
    +
    + + + + + + + + + + + + + +
    FieldDescription
    +natGatewayRef
    + + +Kubernetes core/v1.LocalObjectReference + + +
    +

    NATGatewayRef points to the target NATGateway to scale.

    +
    +minPublicIPs
    + +int32 +

    MinPublicIPs is the minimum number of public IPs to allocate for a NAT Gateway.

    @@ -3363,6 +3705,343 @@ PCIAddress
    +

    NetworkPolicyEgressRule +

    +

    +(Appears on:NetworkPolicySpec) +

    +
    +

    NetworkPolicyEgressRule describes a rule to regulate egress traffic with.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +ports
    + + +[]NetworkPolicyPort + + +
    +

    Ports specifies the list of destination ports that can be called with +this rule. Each item in this list is combined using a logical OR. Empty matches all ports. +As soon as a single item is present, only these ports are allowed.

    +
    +to
    + + +[]NetworkPolicyPeer + + +
    +

    To specifies the list of destinations which the selected network interfaces should be +able to send traffic to. Fields are combined using a logical OR. Empty matches all destinations. +As soon as a single item is present, only these peers are allowed.

    +
    +

    NetworkPolicyIngressRule +

    +

    +(Appears on:NetworkPolicySpec) +

    +
    +

    NetworkPolicyIngressRule describes a rule to regulate ingress traffic with.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +from
    + + +[]NetworkPolicyPeer + + +
    +

    From specifies the list of sources which should be able to send traffic to the +selected network interfaces. Fields are combined using a logical OR. Empty matches all sources. +As soon as a single item is present, only these peers are allowed.

    +
    +ports
    + + +[]NetworkPolicyPort + + +
    +

    Ports specifies the list of ports which should be made accessible for +this rule. Each item in this list is combined using a logical OR. Empty matches all ports. +As soon as a single item is present, only these ports are allowed.

    +
    +

    NetworkPolicyPeer +

    +

    +(Appears on:NetworkPolicyEgressRule, NetworkPolicyIngressRule) +

    +
    +

    NetworkPolicyPeer describes a peer to allow traffic to / from.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +objectSelector
    + + +ObjectSelector + + +
    +

    ObjectSelector selects peers with the given kind matching the label selector. +Exclusive with other peer specifiers.

    +
    +ipBlock
    + + +IPBlock + + +
    +

    IPBlock specifies the ip block from or to which network traffic may come.

    +
    +

    NetworkPolicyPort +

    +

    +(Appears on:NetworkPolicyEgressRule, NetworkPolicyIngressRule, Rule) +

    +
    +

    NetworkPolicyPort describes a port to allow traffic on

    +
    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +protocol
    + + +Kubernetes core/v1.Protocol + + +
    +

    Protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this +field defaults to TCP.

    +
    +port
    + +int32 + +
    +

    The port on the given protocol. If this field is not provided, this matches +all port names and numbers. +If present, only traffic on the specified protocol AND port will be matched.

    +
    +endPort
    + +int32 + +
    +

    EndPort indicates that the range of ports from Port to EndPort, inclusive, +should be allowed by the policy. This field cannot be defined if the port field +is not defined. The endPort must be equal or greater than port.

    +
    +

    NetworkPolicySpec +

    +

    +(Appears on:NetworkPolicy) +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +networkRef
    + + +Kubernetes core/v1.LocalObjectReference + + +
    +

    NetworkRef is the network to regulate using this policy.

    +
    +networkInterfaceSelector
    + + +Kubernetes meta/v1.LabelSelector + + +
    +

    NetworkInterfaceSelector selects the network interfaces that are subject to this policy.

    +
    +priority
    + +int32 + +
    +

    Priority is an optional field that specifies the order in which the policy is applied. +Policies with higher “order” are applied after those with lower +order. If the order is omitted, it may be considered to be “infinite” - i.e. the +policy will be applied last. Policies with identical order will be applied in +alphanumerical order based on the Policy “Name”.

    +
    +ingress
    + + +[]NetworkPolicyIngressRule + + +
    +

    Ingress specifies rules for ingress traffic.

    +
    +egress
    + + +[]NetworkPolicyEgressRule + + +
    +

    Egress specifies rules for egress traffic.

    +
    +policyTypes
    + + +[]PolicyType + + +
    +

    PolicyTypes specifies the types of policies this network policy contains.

    +
    +

    NetworkPolicyTargetRef +

    +

    +(Appears on:TargetNetworkInterface) +

    +
    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +uid
    + + +k8s.io/apimachinery/pkg/types.UID + + +
    +

    UID is the UID of the target.

    +
    +name
    + +string + +
    +

    Name is the name of the target.

    +

    NetworkSpec

    @@ -3603,6 +4282,95 @@ Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.

    +

    ObjectIP +

    +

    +(Appears on:Rule) +

    +
    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +ipFamily
    + + +Kubernetes core/v1.IPFamily + + +
    +

    IPFamily is the IPFamily of the prefix. +If unset but Prefix is set, this can be inferred.

    +
    +prefix
    + + +github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IPPrefix + + +
    +

    Prefix is the prefix of the IP.

    +
    +

    ObjectSelector +

    +

    +(Appears on:NetworkPolicyPeer) +

    +
    +

    ObjectSelector specifies how to select objects of a certain kind.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +kind
    + +string + +
    +

    Kind is the kind of object to select.

    +
    +LabelSelector
    + + +Kubernetes meta/v1.LabelSelector + + +
    +

    +(Members of LabelSelector are embedded into this type.) +

    +

    LabelSelector is the label selector to select objects of the specified Kind by.

    +

    PCIAddress

    @@ -3661,6 +4429,129 @@ string +

    PolicyType +(string alias)

    +

    +(Appears on:NetworkPolicySpec) +

    +
    +

    PolicyType is a type of policy.

    +
    + + + + + + + + + + + + +
    ValueDescription

    "Egress"

    PolicyTypeEgress is a policy that describes egress traffic.

    +

    "Ingress"

    PolicyTypeIngress is a policy that describes ingress traffic.

    +
    +

    Rule +

    +

    +(Appears on:NetworkPolicyRule) +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +ipBlock
    + + +[]IPBlock + + +
    +

    CIDRBlock specifies the CIDR block from which network traffic may come or go.

    +
    +ips
    + + +[]ObjectIP + + +
    +

    ObjectIPs are the object IPs the rule applies to.

    +
    +networkPolicyPorts
    + + +[]NetworkPolicyPort + + +
    +

    NetworkPolicyPorts are the protocol type and ports.

    +
    +

    TargetNetworkInterface +

    +

    +(Appears on:NetworkPolicyRule) +

    +
    +

    TargetNetworkInterface is the target of the network policy.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +ip
    + + +github.com/ironcore-dev/ironcore-net/apimachinery/api/net.IP + + +
    +

    IP is the IP address of the target network interface.

    +
    +targetRef
    + + +NetworkPolicyTargetRef + + +
    +

    TargetRef is the target providing the destination.

    +

    TopologySpreadConstraint

    diff --git a/internal/apis/core/common_types.go b/internal/apis/core/common_types.go index 7c340b42..a48574c2 100644 --- a/internal/apis/core/common_types.go +++ b/internal/apis/core/common_types.go @@ -4,8 +4,6 @@ package core import ( - "net/netip" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -52,22 +50,3 @@ type LocalUIDReference struct { // UID is the UID of the referenced entity. UID types.UID `json:"uid"` } - -// IPPrefix represents a network prefix. -// +nullable -type IPPrefix struct { - netip.Prefix `json:"-"` -} - -func (in *IPPrefix) DeepCopyInto(out *IPPrefix) { - *out = *in -} - -// IPAdd is an IP address. -type IPAdd struct { - netip.Addr `json:"-"` -} - -func (in *IPAdd) DeepCopyInto(out *IPAdd) { - *out = *in -} diff --git a/internal/apis/core/networkpolicy_types.go b/internal/apis/core/networkpolicy_types.go index 5218966a..5c262f37 100644 --- a/internal/apis/core/networkpolicy_types.go +++ b/internal/apis/core/networkpolicy_types.go @@ -9,71 +9,70 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NetworkPolicySpec defines the desired state of NetworkPolicy. type NetworkPolicySpec struct { // NetworkRef is the network to regulate using this policy. - NetworkRef corev1.LocalObjectReference `json:"networkRef"` + NetworkRef corev1.LocalObjectReference // NetworkInterfaceSelector selects the network interfaces that are subject to this policy. - NetworkInterfaceSelector metav1.LabelSelector `json:"networkInterfaceSelector"` + NetworkInterfaceSelector metav1.LabelSelector // Priority is an optional field that specifies the order in which the policy is applied. // Policies with higher "order" are applied after those with lower // order. If the order is omitted, it may be considered to be "infinite" - i.e. the // policy will be applied last. Policies with identical order will be applied in // alphanumerical order based on the Policy "Name". - Priority *int32 `json:"priority,omitempty"` + Priority *int32 // Ingress specifies rules for ingress traffic. - Ingress []NetworkPolicyIngressRule `json:"ingress,omitempty"` + Ingress []NetworkPolicyIngressRule // Egress specifies rules for egress traffic. - Egress []NetworkPolicyEgressRule `json:"egress,omitempty"` + Egress []NetworkPolicyEgressRule // PolicyTypes specifies the types of policies this network policy contains. - PolicyTypes []PolicyType `json:"policyTypes,omitempty"` + PolicyTypes []PolicyType } // NetworkPolicyPort describes a port to allow traffic on type NetworkPolicyPort struct { // Protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this // field defaults to TCP. - Protocol *corev1.Protocol `json:"protocol,omitempty"` + Protocol *corev1.Protocol // The port on the given protocol. If this field is not provided, this matches // all port names and numbers. // If present, only traffic on the specified protocol AND port will be matched. - Port int32 `json:"port,omitempty"` + Port int32 // EndPort indicates that the range of ports from Port to EndPort, inclusive, // should be allowed by the policy. This field cannot be defined if the port field // is not defined. The endPort must be equal or greater than port. - EndPort *int32 `json:"endPort,omitempty" protobuf:"bytes,3,opt,name=endPort"` + EndPort *int32 } // IPBlock specifies an ip block with optional exceptions. type IPBlock struct { // CIDR is a string representing the ip block. - CIDR net.IPPrefix `json:"cidr"` + CIDR net.IPPrefix // Except is a slice of CIDRs that should not be included within the specified CIDR. // Values will be rejected if they are outside CIDR. - Except []net.IPPrefix `json:"except,omitempty"` + Except []net.IPPrefix } // NetworkPolicyPeer describes a peer to allow traffic to / from. type NetworkPolicyPeer struct { // ObjectSelector selects peers with the given kind matching the label selector. // Exclusive with other peer specifiers. - ObjectSelector *ObjectSelector `json:"objectSelector,omitempty"` + ObjectSelector *ObjectSelector // IPBlock specifies the ip block from or to which network traffic may come. - IPBlock *IPBlock `json:"ipBlock,omitempty"` + IPBlock *IPBlock } // NetworkPolicyIngressRule describes a rule to regulate ingress traffic with. type NetworkPolicyIngressRule struct { - // Ports specifies the list of ports which should be made accessible for - // this rule. Each item in this list is combined using a logical OR. Empty matches all ports. - // As soon as a single item is present, only these ports are allowed. - Ports []NetworkPolicyPort `json:"ports,omitempty"` // From specifies the list of sources which should be able to send traffic to the // selected network interfaces. Fields are combined using a logical OR. Empty matches all sources. // As soon as a single item is present, only these peers are allowed. - From []NetworkPolicyPeer `json:"from,omitempty"` + From []NetworkPolicyPeer + // Ports specifies the list of ports which should be made accessible for + // this rule. Each item in this list is combined using a logical OR. Empty matches all ports. + // As soon as a single item is present, only these ports are allowed. + Ports []NetworkPolicyPort } // NetworkPolicyEgressRule describes a rule to regulate egress traffic with. @@ -81,11 +80,11 @@ type NetworkPolicyEgressRule struct { // Ports specifies the list of destination ports that can be called with // this rule. Each item in this list is combined using a logical OR. Empty matches all ports. // As soon as a single item is present, only these ports are allowed. - Ports []NetworkPolicyPort `json:"ports,omitempty"` + Ports []NetworkPolicyPort // To specifies the list of destinations which the selected network interfaces should be // able to send traffic to. Fields are combined using a logical OR. Empty matches all destinations. // As soon as a single item is present, only these peers are allowed. - To []NetworkPolicyPeer `json:"to,omitempty"` + To []NetworkPolicyPeer } // PolicyType is a type of policy. @@ -98,22 +97,22 @@ const ( PolicyTypeEgress PolicyType = "Egress" ) -// +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient -// NetworkPolicy is the Schema for the networkpolicies API +// NetworkPolicy is the Schema for the networkpolicies API. type NetworkPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta + metav1.ObjectMeta - Spec NetworkPolicySpec `json:"spec,omitempty"` + Spec NetworkPolicySpec } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // NetworkPolicyList contains a list of NetworkPolicy. type NetworkPolicyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []NetworkPolicy `json:"items"` + metav1.TypeMeta + metav1.ListMeta + Items []NetworkPolicy } diff --git a/internal/apis/core/networkpolicyrule_types.go b/internal/apis/core/networkpolicyrule_types.go index 361880df..a23ec34f 100644 --- a/internal/apis/core/networkpolicyrule_types.go +++ b/internal/apis/core/networkpolicyrule_types.go @@ -4,6 +4,7 @@ package core import ( + "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -14,58 +15,58 @@ import ( // NetworkPolicyRule is the schema for the networkpolicyrules API. type NetworkPolicyRule struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta + metav1.ObjectMeta // NetworkRef is the network the load balancer is assigned to. - NetworkRef LocalUIDReference `json:"networkRef"` - + NetworkRef LocalUIDReference // Targets are the targets of the network policy. - Targets []TargetNetworkInterface `json:"targets"` - + Targets []TargetNetworkInterface + // Priority is an optional field that specifies the order in which the policy is applied. + Priority *int32 // IngressRules are the ingress rules. - IngressRules []Rule `json:"ingressRule,omitempty"` + IngressRules []Rule // EgressRules are the egress rules. - EgressRules []Rule `json:"egressRule,omitempty"` + EgressRules []Rule } // TargetNetworkInterface is the target of the network policy. type TargetNetworkInterface struct { // IP is the IP address of the target network interface. - IP IPAdd `json:"ip"` + IP net.IP // TargetRef is the target providing the destination. - TargetRef *NetworkPolicyTargetRef `json:"targetRef,omitempty"` + TargetRef *NetworkPolicyTargetRef } type NetworkPolicyTargetRef struct { // UID is the UID of the target. - UID types.UID `json:"uid"` + UID types.UID // Name is the name of the target. - Name string `json:"name"` + Name string } type Rule struct { // CIDRBlock specifies the CIDR block from which network traffic may come or go. - CIDRBlock []IPBlock `json:"ipBlock,omitempty"` + CIDRBlock []IPBlock // ObjectIPs are the object IPs the rule applies to. - ObjectIPs []ObjectIP `json:"ips,omitempty"` + ObjectIPs []ObjectIP // NetworkPolicyPorts are the protocol type and ports. - NetworkPolicyPorts []NetworkPolicyPort `json:"networkPolicyPorts,omitempty"` + NetworkPolicyPorts []NetworkPolicyPort } type ObjectIP struct { // IPFamily is the IPFamily of the prefix. // If unset but Prefix is set, this can be inferred. - IPFamily corev1.IPFamily `json:"ipFamily,omitempty"` + IPFamily corev1.IPFamily // Prefix is the prefix of the IP. - Prefix *IPPrefix `json:"prefix,omitempty"` + Prefix net.IPPrefix } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // NetworkPolicyRulesList contains a list of NetworkPolicyRule. type NetworkPolicyRuleList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []NetworkPolicyRule `json:"items"` + metav1.TypeMeta + metav1.ListMeta + Items []NetworkPolicyRule } diff --git a/internal/apis/core/v1alpha1/zz_generated.conversion.go b/internal/apis/core/v1alpha1/zz_generated.conversion.go index 332a9963..39224562 100644 --- a/internal/apis/core/v1alpha1/zz_generated.conversion.go +++ b/internal/apis/core/v1alpha1/zz_generated.conversion.go @@ -88,16 +88,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1alpha1.IPAdd)(nil), (*core.IPAdd)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_IPAdd_To_core_IPAdd(a.(*v1alpha1.IPAdd), b.(*core.IPAdd), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*core.IPAdd)(nil), (*v1alpha1.IPAdd)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_core_IPAdd_To_v1alpha1_IPAdd(a.(*core.IPAdd), b.(*v1alpha1.IPAdd), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*v1alpha1.IPAddress)(nil), (*core.IPAddress)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_IPAddress_To_core_IPAddress(a.(*v1alpha1.IPAddress), b.(*core.IPAddress), scope) }); err != nil { @@ -168,16 +158,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1alpha1.IPPrefix)(nil), (*core.IPPrefix)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_IPPrefix_To_core_IPPrefix(a.(*v1alpha1.IPPrefix), b.(*core.IPPrefix), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*core.IPPrefix)(nil), (*v1alpha1.IPPrefix)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_core_IPPrefix_To_v1alpha1_IPPrefix(a.(*core.IPPrefix), b.(*v1alpha1.IPPrefix), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*v1alpha1.IPSpec)(nil), (*core.IPSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_IPSpec_To_core_IPSpec(a.(*v1alpha1.IPSpec), b.(*core.IPSpec), scope) }); err != nil { @@ -1065,26 +1045,6 @@ func Convert_core_IP_To_v1alpha1_IP(in *core.IP, out *v1alpha1.IP, s conversion. return autoConvert_core_IP_To_v1alpha1_IP(in, out, s) } -func autoConvert_v1alpha1_IPAdd_To_core_IPAdd(in *v1alpha1.IPAdd, out *core.IPAdd, s conversion.Scope) error { - out.Addr = in.Addr - return nil -} - -// Convert_v1alpha1_IPAdd_To_core_IPAdd is an autogenerated conversion function. -func Convert_v1alpha1_IPAdd_To_core_IPAdd(in *v1alpha1.IPAdd, out *core.IPAdd, s conversion.Scope) error { - return autoConvert_v1alpha1_IPAdd_To_core_IPAdd(in, out, s) -} - -func autoConvert_core_IPAdd_To_v1alpha1_IPAdd(in *core.IPAdd, out *v1alpha1.IPAdd, s conversion.Scope) error { - out.Addr = in.Addr - return nil -} - -// Convert_core_IPAdd_To_v1alpha1_IPAdd is an autogenerated conversion function. -func Convert_core_IPAdd_To_v1alpha1_IPAdd(in *core.IPAdd, out *v1alpha1.IPAdd, s conversion.Scope) error { - return autoConvert_core_IPAdd_To_v1alpha1_IPAdd(in, out, s) -} - func autoConvert_v1alpha1_IPAddress_To_core_IPAddress(in *v1alpha1.IPAddress, out *core.IPAddress, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha1_IPAddressSpec_To_core_IPAddressSpec(&in.Spec, &out.Spec, s); err != nil { @@ -1257,26 +1217,6 @@ func Convert_core_IPList_To_v1alpha1_IPList(in *core.IPList, out *v1alpha1.IPLis return autoConvert_core_IPList_To_v1alpha1_IPList(in, out, s) } -func autoConvert_v1alpha1_IPPrefix_To_core_IPPrefix(in *v1alpha1.IPPrefix, out *core.IPPrefix, s conversion.Scope) error { - out.Prefix = in.Prefix - return nil -} - -// Convert_v1alpha1_IPPrefix_To_core_IPPrefix is an autogenerated conversion function. -func Convert_v1alpha1_IPPrefix_To_core_IPPrefix(in *v1alpha1.IPPrefix, out *core.IPPrefix, s conversion.Scope) error { - return autoConvert_v1alpha1_IPPrefix_To_core_IPPrefix(in, out, s) -} - -func autoConvert_core_IPPrefix_To_v1alpha1_IPPrefix(in *core.IPPrefix, out *v1alpha1.IPPrefix, s conversion.Scope) error { - out.Prefix = in.Prefix - return nil -} - -// Convert_core_IPPrefix_To_v1alpha1_IPPrefix is an autogenerated conversion function. -func Convert_core_IPPrefix_To_v1alpha1_IPPrefix(in *core.IPPrefix, out *v1alpha1.IPPrefix, s conversion.Scope) error { - return autoConvert_core_IPPrefix_To_v1alpha1_IPPrefix(in, out, s) -} - func autoConvert_v1alpha1_IPSpec_To_core_IPSpec(in *v1alpha1.IPSpec, out *core.IPSpec, s conversion.Scope) error { out.Type = core.IPType(in.Type) out.IPFamily = corev1.IPFamily(in.IPFamily) @@ -2490,8 +2430,8 @@ func Convert_core_NetworkPolicyEgressRule_To_v1alpha1_NetworkPolicyEgressRule(in } func autoConvert_v1alpha1_NetworkPolicyIngressRule_To_core_NetworkPolicyIngressRule(in *v1alpha1.NetworkPolicyIngressRule, out *core.NetworkPolicyIngressRule, s conversion.Scope) error { - out.Ports = *(*[]core.NetworkPolicyPort)(unsafe.Pointer(&in.Ports)) out.From = *(*[]core.NetworkPolicyPeer)(unsafe.Pointer(&in.From)) + out.Ports = *(*[]core.NetworkPolicyPort)(unsafe.Pointer(&in.Ports)) return nil } @@ -2501,8 +2441,8 @@ func Convert_v1alpha1_NetworkPolicyIngressRule_To_core_NetworkPolicyIngressRule( } func autoConvert_core_NetworkPolicyIngressRule_To_v1alpha1_NetworkPolicyIngressRule(in *core.NetworkPolicyIngressRule, out *v1alpha1.NetworkPolicyIngressRule, s conversion.Scope) error { - out.Ports = *(*[]v1alpha1.NetworkPolicyPort)(unsafe.Pointer(&in.Ports)) out.From = *(*[]v1alpha1.NetworkPolicyPeer)(unsafe.Pointer(&in.From)) + out.Ports = *(*[]v1alpha1.NetworkPolicyPort)(unsafe.Pointer(&in.Ports)) return nil } @@ -2585,6 +2525,7 @@ func autoConvert_v1alpha1_NetworkPolicyRule_To_core_NetworkPolicyRule(in *v1alph return err } out.Targets = *(*[]core.TargetNetworkInterface)(unsafe.Pointer(&in.Targets)) + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) out.IngressRules = *(*[]core.Rule)(unsafe.Pointer(&in.IngressRules)) out.EgressRules = *(*[]core.Rule)(unsafe.Pointer(&in.EgressRules)) return nil @@ -2601,6 +2542,7 @@ func autoConvert_core_NetworkPolicyRule_To_v1alpha1_NetworkPolicyRule(in *core.N return err } out.Targets = *(*[]v1alpha1.TargetNetworkInterface)(unsafe.Pointer(&in.Targets)) + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) out.IngressRules = *(*[]v1alpha1.Rule)(unsafe.Pointer(&in.IngressRules)) out.EgressRules = *(*[]v1alpha1.Rule)(unsafe.Pointer(&in.EgressRules)) return nil @@ -2901,7 +2843,7 @@ func Convert_core_NodeStatus_To_v1alpha1_NodeStatus(in *core.NodeStatus, out *v1 func autoConvert_v1alpha1_ObjectIP_To_core_ObjectIP(in *v1alpha1.ObjectIP, out *core.ObjectIP, s conversion.Scope) error { out.IPFamily = corev1.IPFamily(in.IPFamily) - out.Prefix = (*core.IPPrefix)(unsafe.Pointer(in.Prefix)) + out.Prefix = in.Prefix return nil } @@ -2912,7 +2854,7 @@ func Convert_v1alpha1_ObjectIP_To_core_ObjectIP(in *v1alpha1.ObjectIP, out *core func autoConvert_core_ObjectIP_To_v1alpha1_ObjectIP(in *core.ObjectIP, out *v1alpha1.ObjectIP, s conversion.Scope) error { out.IPFamily = corev1.IPFamily(in.IPFamily) - out.Prefix = (*v1alpha1.IPPrefix)(unsafe.Pointer(in.Prefix)) + out.Prefix = in.Prefix return nil } @@ -2994,9 +2936,7 @@ func Convert_core_Rule_To_v1alpha1_Rule(in *core.Rule, out *v1alpha1.Rule, s con } func autoConvert_v1alpha1_TargetNetworkInterface_To_core_TargetNetworkInterface(in *v1alpha1.TargetNetworkInterface, out *core.TargetNetworkInterface, s conversion.Scope) error { - if err := Convert_v1alpha1_IPAdd_To_core_IPAdd(&in.IP, &out.IP, s); err != nil { - return err - } + out.IP = in.IP out.TargetRef = (*core.NetworkPolicyTargetRef)(unsafe.Pointer(in.TargetRef)) return nil } @@ -3007,9 +2947,7 @@ func Convert_v1alpha1_TargetNetworkInterface_To_core_TargetNetworkInterface(in * } func autoConvert_core_TargetNetworkInterface_To_v1alpha1_TargetNetworkInterface(in *core.TargetNetworkInterface, out *v1alpha1.TargetNetworkInterface, s conversion.Scope) error { - if err := Convert_core_IPAdd_To_v1alpha1_IPAdd(&in.IP, &out.IP, s); err != nil { - return err - } + out.IP = in.IP out.TargetRef = (*v1alpha1.NetworkPolicyTargetRef)(unsafe.Pointer(in.TargetRef)) return nil } diff --git a/internal/apis/core/validation/networkpolicy.go b/internal/apis/core/validation/networkpolicy.go index ebba7fad..896ef857 100644 --- a/internal/apis/core/validation/networkpolicy.go +++ b/internal/apis/core/validation/networkpolicy.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 package validation @@ -67,7 +67,6 @@ func validateNetworkPolicySpec(spec *core.NetworkPolicySpec, fldPath *field.Path var supportedIngressObjectSelectorKinds = sets.New[string]( "NetworkInterface", "LoadBalancer", - "VirtualIP", ) func validateNetworkPolicyIngressRule(rule *core.NetworkPolicyIngressRule, fldPath *field.Path) field.ErrorList { @@ -91,7 +90,6 @@ func validateNetworkPolicyIngressRule(rule *core.NetworkPolicyIngressRule, fldPa var supportedEgressObjectSelectorKinds = sets.New[string]( "NetworkInterface", "LoadBalancer", - "VirtualIP", ) func validateNetworkPolicyEgressRule(rule *core.NetworkPolicyEgressRule, fldPath *field.Path) field.ErrorList { diff --git a/internal/apis/core/zz_generated.deepcopy.go b/internal/apis/core/zz_generated.deepcopy.go index e71844cb..e17e9590 100644 --- a/internal/apis/core/zz_generated.deepcopy.go +++ b/internal/apis/core/zz_generated.deepcopy.go @@ -173,16 +173,6 @@ func (in *IP) DeepCopyObject() runtime.Object { return nil } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAdd. -func (in *IPAdd) DeepCopy() *IPAdd { - if in == nil { - return nil - } - out := new(IPAdd) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAddress) DeepCopyInto(out *IPAddress) { *out = *in @@ -350,16 +340,6 @@ func (in *IPList) DeepCopyObject() runtime.Object { return nil } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPrefix. -func (in *IPPrefix) DeepCopy() *IPPrefix { - if in == nil { - return nil - } - out := new(IPPrefix) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPSpec) DeepCopyInto(out *IPSpec) { *out = *in @@ -1642,16 +1622,16 @@ func (in *NetworkPolicyEgressRule) DeepCopy() *NetworkPolicyEgressRule { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicyIngressRule) DeepCopyInto(out *NetworkPolicyIngressRule) { *out = *in - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]NetworkPolicyPort, len(*in)) + if in.From != nil { + in, out := &in.From, &out.From + *out = make([]NetworkPolicyPeer, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.From != nil { - in, out := &in.From, &out.From - *out = make([]NetworkPolicyPeer, len(*in)) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]NetworkPolicyPort, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -1767,6 +1747,11 @@ func (in *NetworkPolicyRule) DeepCopyInto(out *NetworkPolicyRule) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int32) + **out = **in + } if in.IngressRules != nil { in, out := &in.IngressRules, &out.IngressRules *out = make([]Rule, len(*in)) @@ -2116,10 +2101,7 @@ func (in *NodeStatus) DeepCopy() *NodeStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectIP) DeepCopyInto(out *ObjectIP) { *out = *in - if in.Prefix != nil { - in, out := &in.Prefix, &out.Prefix - *out = (*in).DeepCopy() - } + in.Prefix.DeepCopyInto(&out.Prefix) return } diff --git a/internal/apiserver/apiserver.go b/internal/apiserver/apiserver.go index 344fbbfa..f028b9de 100644 --- a/internal/apiserver/apiserver.go +++ b/internal/apiserver/apiserver.go @@ -26,6 +26,8 @@ import ( "github.com/ironcore-dev/ironcore-net/internal/registry/network/networkidallocator" "github.com/ironcore-dev/ironcore-net/internal/registry/networkid" "github.com/ironcore-dev/ironcore-net/internal/registry/networkinterface" + "github.com/ironcore-dev/ironcore-net/internal/registry/networkpolicy" + "github.com/ironcore-dev/ironcore-net/internal/registry/networkpolicyrule" "github.com/ironcore-dev/ironcore-net/internal/registry/node" ironcoreserializer "github.com/ironcore-dev/ironcore-net/internal/serializer" corev1 "k8s.io/api/core/v1" @@ -214,6 +216,20 @@ func (c completedConfig) New() (*IronCoreServer, error) { v1alpha1storage["loadbalancerroutings"] = loadBalancerRoutingStorage.LoadBalancerRouting + networkPolicyStorage, err := networkpolicy.NewStorage(Scheme, c.GenericConfig.RESTOptionsGetter) + if err != nil { + return nil, err + } + + v1alpha1storage["networkpolicies"] = networkPolicyStorage.NetworkPolicy + + networkPolicyRuleStorage, err := networkpolicyrule.NewStorage(Scheme, c.GenericConfig.RESTOptionsGetter) + if err != nil { + return nil, err + } + + v1alpha1storage["networkpolicyrules"] = networkPolicyRuleStorage.NetworkPolicyRule + natGatewayStorage, err := natgateway.NewStorage(Scheme, c.GenericConfig.RESTOptionsGetter, ipAllocByFamily) if err != nil { return nil, err diff --git a/internal/controllers/networkpolicy_controller.go b/internal/controllers/networkpolicy_controller.go new file mode 100644 index 00000000..ed7acf1c --- /dev/null +++ b/internal/controllers/networkpolicy_controller.go @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package controllers + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type NetworkPolicyReconciler struct { + client.Client +} + +//+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkpolicies,verbs=get;list;watch +//+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkpolicyrules,verbs=get;list;watch;create;update;patch;delete + +func (r *NetworkPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + networkPolicy := &v1alpha1.NetworkPolicy{} + if err := r.Get(ctx, req.NamespacedName, networkPolicy); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, err + } + + networkPolicyRule := &v1alpha1.NetworkPolicyRule{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: req.Name, + }, + } + if err := r.Delete(ctx, networkPolicyRule); client.IgnoreNotFound(err) != nil { + return ctrl.Result{}, fmt.Errorf("error deleting network policy rule: %w", err) + } + return ctrl.Result{}, nil + } + + return r.reconcileExists(ctx, log, networkPolicy) +} + +func (r *NetworkPolicyReconciler) reconcileExists(ctx context.Context, log logr.Logger, networkPolicy *v1alpha1.NetworkPolicy) (ctrl.Result, error) { + if !networkPolicy.DeletionTimestamp.IsZero() { + return r.delete(ctx, log, networkPolicy) + } + return r.reconcile(ctx, log, networkPolicy) +} + +func (r *NetworkPolicyReconciler) delete(ctx context.Context, log logr.Logger, networkPolicy *v1alpha1.NetworkPolicy) (ctrl.Result, error) { + _, _ = ctx, networkPolicy + log.V(1).Info("Delete") + log.V(1).Info("Deleted") + return ctrl.Result{}, nil +} + +func (r *NetworkPolicyReconciler) reconcile(_ context.Context, log logr.Logger, _ *v1alpha1.NetworkPolicy) (ctrl.Result, error) { + log.V(1).Info("Reconcile") + //reconcile logic + + log.V(1).Info("Reconciled") + return ctrl.Result{}, nil +} + +func (r *NetworkPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.NetworkPolicy{}). + Complete(r) +} diff --git a/metalnetlet/controllers/networkinterface_controller.go b/metalnetlet/controllers/networkinterface_controller.go index 6dc8be97..e18b8bc3 100644 --- a/metalnetlet/controllers/networkinterface_controller.go +++ b/metalnetlet/controllers/networkinterface_controller.go @@ -8,17 +8,20 @@ import ( "fmt" "github.com/go-logr/logr" + "github.com/google/uuid" "github.com/ironcore-dev/controller-utils/clientutils" "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" metalnetletclient "github.com/ironcore-dev/ironcore-net/metalnetlet/client" utilhandler "github.com/ironcore-dev/ironcore-net/metalnetlet/handler" + "github.com/ironcore-dev/ironcore/utils/generic" utilslices "github.com/ironcore-dev/ironcore/utils/slices" metalnetv1alpha1 "github.com/ironcore-dev/metalnet/api/v1alpha1" "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -47,6 +50,8 @@ type NetworkInterfaceReconciler struct { //+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=loadbalancers,verbs=get;list;watch //+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=nattables,verbs=get;list;watch //+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=natgateways,verbs=get;list;watch +//+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkpolicies,verbs=get;list;watch +//+kubebuilder:rbac:groups=core.apinet.ironcore.dev,resources=networkpolicyrules,verbs=get;list;watch //+cluster=metalnet:kubebuilder:rbac:groups=networking.metalnet.ironcore.dev,resources=networkinterfaces,verbs=get;list;watch;create;update;patch;delete;deletecollection //+cluster=metalnet:kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch @@ -161,6 +166,130 @@ func (r *NetworkInterfaceReconciler) getLoadBalancerTargetsForNetworkInterface(c return ips, nil } +func (r *NetworkInterfaceReconciler) getNetworkPolicyRulesForNetworkInterface(ctx context.Context, nic *v1alpha1.NetworkInterface) ([]metalnetv1alpha1.FirewallRule, error) { + var firewallRules []metalnetv1alpha1.FirewallRule + + npRuleList := &v1alpha1.NetworkPolicyRuleList{} + if err := r.List(ctx, npRuleList, + client.InNamespace(nic.Namespace), + ); err != nil { + return nil, fmt.Errorf("error listing network policy rules: %w", err) + } + + for _, npRule := range npRuleList.Items { + hasDst := slices.ContainsFunc(npRule.Targets, + func(target v1alpha1.TargetNetworkInterface) bool { + return slices.Contains(nic.Spec.IPs, target.IP) + }, + ) + if hasDst { + rules := getFirewallRulesFromNetworkPolicyRule(&npRule) + firewallRules = append(firewallRules, rules...) + } + } + + return firewallRules, nil +} + +func getFirewallRulesFromNetworkPolicyRule(npRule *v1alpha1.NetworkPolicyRule) []metalnetv1alpha1.FirewallRule { + var firewallRules []metalnetv1alpha1.FirewallRule + priority := npRule.Priority + + for _, ingressRule := range npRule.IngressRules { + rules := extractFirewallRulesFromRule(ingressRule, metalnetv1alpha1.FirewallRuleDirectionIngress, priority) + firewallRules = append(firewallRules, rules...) + } + + for _, egressRule := range npRule.EgressRules { + rules := extractFirewallRulesFromRule(egressRule, metalnetv1alpha1.FirewallRuleDirectionEgress, priority) + firewallRules = append(firewallRules, rules...) + } + + return firewallRules +} + +func extractFirewallRulesFromRule(rule v1alpha1.Rule, direction metalnetv1alpha1.FirewallRuleDirection, priority *int32) []metalnetv1alpha1.FirewallRule { + var firewallRules []metalnetv1alpha1.FirewallRule + + for _, port := range rule.NetworkPolicyPorts { + firewallRule := metalnetv1alpha1.FirewallRule{ + FirewallRuleID: types.UID(uuid.New().String()), + Direction: direction, + Action: metalnetv1alpha1.FirewallRuleActionAccept, + Priority: priority, + IpFamily: corev1.IPv4Protocol, //TODO: later support for IPv6 + ProtocolMatch: &metalnetv1alpha1.ProtocolMatch{}, + } + + switch *port.Protocol { + case corev1.ProtocolTCP: + firewallRule.ProtocolMatch.ProtocolType = generic.Pointer(metalnetv1alpha1.FirewallRuleProtocolTypeTCP) + case corev1.ProtocolUDP: + firewallRule.ProtocolMatch.ProtocolType = generic.Pointer(metalnetv1alpha1.FirewallRuleProtocolTypeUDP) + //TODO: no support for SCTP protocol in metalnetlet and metalnetlet FirewallRuleProtocolTypeICMP is not defined in ironcore + } + + if port.Port != 0 { + if direction == metalnetv1alpha1.FirewallRuleDirectionIngress { + firewallRule.ProtocolMatch.PortRange = &metalnetv1alpha1.PortMatch{SrcPort: &port.Port} + } else { + firewallRule.ProtocolMatch.PortRange = &metalnetv1alpha1.PortMatch{DstPort: &port.Port} + } + if port.EndPort != nil { + if direction == metalnetv1alpha1.FirewallRuleDirectionIngress { + firewallRule.ProtocolMatch.PortRange.EndSrcPort = *port.EndPort + } else { + firewallRule.ProtocolMatch.PortRange.EndDstPort = *port.EndPort + } + } + } + + for _, cidrBlock := range rule.CIDRBlock { + cidrFirewallRule := firewallRule + cidrFirewallRule.FirewallRuleID = types.UID(uuid.New().String()) + + if direction == metalnetv1alpha1.FirewallRuleDirectionIngress { + cidrFirewallRule.SourcePrefix = &metalnetv1alpha1.IPPrefix{Prefix: cidrBlock.CIDR.Prefix} + } else { + cidrFirewallRule.DestinationPrefix = &metalnetv1alpha1.IPPrefix{Prefix: cidrBlock.CIDR.Prefix} + } + + firewallRules = append(firewallRules, cidrFirewallRule) + + if len(cidrBlock.Except) > 0 { + for _, exceptCIDR := range cidrBlock.Except { + exceptFirewallRule := cidrFirewallRule + exceptFirewallRule.FirewallRuleID = types.UID(uuid.New().String()) + exceptFirewallRule.Action = metalnetv1alpha1.FirewallRuleActionDeny + + if direction == metalnetv1alpha1.FirewallRuleDirectionIngress { + exceptFirewallRule.SourcePrefix = &metalnetv1alpha1.IPPrefix{Prefix: exceptCIDR.Prefix} + } else { + exceptFirewallRule.DestinationPrefix = &metalnetv1alpha1.IPPrefix{Prefix: exceptCIDR.Prefix} + } + + firewallRules = append(firewallRules, exceptFirewallRule) + } + } + } + + for _, objectIP := range rule.ObjectIPs { + objectIPFirewallRule := firewallRule + objectIPFirewallRule.FirewallRuleID = types.UID(uuid.New().String()) + + if direction == metalnetv1alpha1.FirewallRuleDirectionIngress { + objectIPFirewallRule.SourcePrefix = &metalnetv1alpha1.IPPrefix{Prefix: objectIP.Prefix.Prefix} + } else { + objectIPFirewallRule.DestinationPrefix = &metalnetv1alpha1.IPPrefix{Prefix: objectIP.Prefix.Prefix} + } + + firewallRules = append(firewallRules, objectIPFirewallRule) + } + } + + return firewallRules +} + func (r *NetworkInterfaceReconciler) getNATDetailsForNetworkInterface( ctx context.Context, nic *v1alpha1.NetworkInterface, @@ -345,6 +474,12 @@ func (r *NetworkInterfaceReconciler) applyMetalnetNic(ctx context.Context, log l return nil, false, fmt.Errorf("error getting load balancer targets: %w", err) } + log.V(1).Info("Getting network policy rules") + npRules, err := r.getNetworkPolicyRulesForNetworkInterface(ctx, nic) + if err != nil { + return nil, false, fmt.Errorf("error getting network policy rules: %w", err) + } + log.V(1).Info("Getting NAT IPs") natIPs, err := r.getNATDetailsForNetworkInterface(ctx, nic) if err != nil { @@ -370,6 +505,7 @@ func (r *NetworkInterfaceReconciler) applyMetalnetNic(ctx context.Context, log l LoadBalancerTargets: ipsToMetalnetIPPrefixes(targets), NAT: workaroundMetalnetNoIPv6NATDetailsToNATDetailsPointer(natIPs), NodeName: &metalnetNodeName, + FirewallRules: npRules, }, } log.V(1).Info("Applying metalnet network interface") @@ -475,6 +611,69 @@ func (r *NetworkInterfaceReconciler) enqueueByLoadBalancer() handler.EventHandle }) } +func (r *NetworkInterfaceReconciler) reconcileRequestsByNetworkPolicyRule( + ctx context.Context, + log logr.Logger, + networkPolicyRule *v1alpha1.NetworkPolicyRule, +) []ctrl.Request { + nicList := &v1alpha1.NetworkInterfaceList{} + if err := r.List(ctx, nicList, + client.InNamespace(networkPolicyRule.Namespace), + ); err != nil { + log.Error(err, "Error listing network interfaces") + return nil + } + + metalnetNodeList := &corev1.NodeList{} + if err := r.MetalnetClient.List(ctx, metalnetNodeList); err != nil { + log.Error(err, "Error listing metalnet nodes") + return nil + } + + targetIPs := utilslices.ToSetFunc(networkPolicyRule.Targets, + func(target v1alpha1.TargetNetworkInterface) net.IP { return target.IP }, + ) + + var reqs []ctrl.Request + for _, nic := range nicList.Items { + if _, err := ParseNodeName(r.PartitionName, nic.Spec.NodeRef.Name); err != nil { + continue + } + + if targetIPs.HasAny(nic.Spec.IPs...) { + reqs = append(reqs, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(&nic)}) + } + } + return reqs +} + +func (r *NetworkInterfaceReconciler) enqueueByNetworkPolicyRule() handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrl.Request { + networkPolicyRule := obj.(*v1alpha1.NetworkPolicyRule) + log := ctrl.LoggerFrom(ctx) + + return r.reconcileRequestsByNetworkPolicyRule(ctx, log, networkPolicyRule) + }) +} + +func (r *NetworkInterfaceReconciler) enqueueByNetworkPolicy() handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrl.Request { + networkPolicy := obj.(*v1alpha1.NetworkPolicy) + log := ctrl.LoggerFrom(ctx) + + networkPolicyKey := client.ObjectKeyFromObject(networkPolicy) + networkPolicyRule := &v1alpha1.NetworkPolicyRule{} + if err := r.Get(ctx, networkPolicyKey, networkPolicyRule); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "Error getting network policy rule") + } + return nil + } + + return r.reconcileRequestsByNetworkPolicyRule(ctx, log, networkPolicyRule) + }) +} + func (r *NetworkInterfaceReconciler) enqueueByMetalnetNode() handler.EventHandler { return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrl.Request { metalnetNode := obj.(*corev1.Node) @@ -519,6 +718,14 @@ func (r *NetworkInterfaceReconciler) SetupWithManager(mgr ctrl.Manager, metalnet &v1alpha1.LoadBalancerRouting{}, r.enqueueByLoadBalancerRouting(), ). + Watches( + &v1alpha1.NetworkPolicy{}, + r.enqueueByNetworkPolicy(), + ). + Watches( + &v1alpha1.NetworkPolicyRule{}, + r.enqueueByNetworkPolicyRule(), + ). WatchesRawSource( source.Kind(metalnetCache, &metalnetv1alpha1.NetworkInterface{}), utilhandler.EnqueueRequestForSource(r.Scheme(), r.RESTMapper(), &v1alpha1.NetworkInterface{}), diff --git a/metalnetlet/controllers/networkinterface_controller_test.go b/metalnetlet/controllers/networkinterface_controller_test.go index 406db22f..7215b0e0 100644 --- a/metalnetlet/controllers/networkinterface_controller_test.go +++ b/metalnetlet/controllers/networkinterface_controller_test.go @@ -8,10 +8,13 @@ import ( "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" "github.com/ironcore-dev/ironcore-net/apimachinery/api/net" + "github.com/ironcore-dev/ironcore/utils/generic" . "github.com/ironcore-dev/ironcore/utils/testing" metalnetv1alpha1 "github.com/ironcore-dev/metalnet/api/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,7 +29,7 @@ var _ = Describe("NetworkInterfaceController", func() { metalnetNode := SetupMetalnetNode() network := SetupNetwork(ns) - It("should create a metalnet network for a network", func(ctx SpecContext) { + It("should create a metalnet network interface for a network interface", func(ctx SpecContext) { By("creating a network") By("creating a network interface") @@ -34,6 +37,9 @@ var _ = Describe("NetworkInterfaceController", func() { ObjectMeta: metav1.ObjectMeta{ Namespace: ns.Name, GenerateName: "nic-", + Labels: map[string]string{ + "app": "target", + }, }, Spec: v1alpha1.NetworkInterfaceSpec{ NodeRef: corev1.LocalObjectReference{ @@ -88,6 +94,75 @@ var _ = Describe("NetworkInterfaceController", func() { } Expect(k8sClient.Create(ctx, loadBalancerRouting)).To(Succeed()) + By("creating a network policy rule") + np := &v1alpha1.NetworkPolicyRule{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "network-policy-", + }, + NetworkRef: v1alpha1.LocalUIDReference{Name: network.Name, UID: network.UID}, + Targets: []v1alpha1.TargetNetworkInterface{ + { + IP: net.MustParseIP("10.0.0.1"), + TargetRef: &v1alpha1.NetworkPolicyTargetRef{ + UID: nic.UID, + Name: nic.Name, + }, + }, + }, + Priority: generic.Pointer(int32(3000)), + IngressRules: []v1alpha1.Rule{ + { + CIDRBlock: []v1alpha1.IPBlock{ + { + CIDR: net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.1.0/24")}, + Except: []net.IPPrefix{ + {Prefix: netip.MustParsePrefix("192.168.2.100/32")}, + }, + }, + }, + ObjectIPs: []v1alpha1.ObjectIP{ + { + Prefix: net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.2.0/24")}, + }, + }, + NetworkPolicyPorts: []v1alpha1.NetworkPolicyPort{ + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 8080, + EndPort: generic.Pointer(int32(8090)), + }, + }, + }, + }, + EgressRules: []v1alpha1.Rule{ + { + CIDRBlock: []v1alpha1.IPBlock{ + { + CIDR: net.IPPrefix{Prefix: netip.MustParsePrefix("10.0.0.0/16")}, + }, + }, + ObjectIPs: []v1alpha1.ObjectIP{ + { + Prefix: net.IPPrefix{Prefix: netip.MustParsePrefix("192.168.178.60/32")}, + }, + }, + NetworkPolicyPorts: []v1alpha1.NetworkPolicyPort{ + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 8095, + }, + { + Protocol: generic.Pointer(corev1.ProtocolTCP), + Port: 9000, + EndPort: generic.Pointer(int32(9010)), + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, np)).To(Succeed()) + By("waiting for the network interface to have a finalizer") Eventually(Object(nic)).Should(HaveField("Finalizers", []string{PartitionFinalizer(partitionName)})) @@ -98,15 +173,160 @@ var _ = Describe("NetworkInterfaceController", func() { Name: string(nic.UID), }, } - Eventually(Object(metalnetNic)).Should(HaveField("Spec", metalnetv1alpha1.NetworkInterfaceSpec{ - NetworkRef: corev1.LocalObjectReference{Name: string(network.UID)}, - IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, - IPs: []metalnetv1alpha1.IP{metalnetv1alpha1.MustParseIP("10.0.0.1")}, - LoadBalancerTargets: []metalnetv1alpha1.IPPrefix{ - {Prefix: netip.PrefixFrom(loadBalancer.Spec.IPs[0].IP.Addr, 32)}, - }, - NodeName: &metalnetNode.Name, - })) + + Eventually(Object(metalnetNic)).Should(SatisfyAll( + HaveField("Spec.NetworkRef", Equal(corev1.LocalObjectReference{Name: string(network.UID)})), + HaveField("Spec.IPFamilies", ConsistOf(corev1.IPv4Protocol)), + HaveField("Spec.IPs", ConsistOf(metalnetv1alpha1.MustParseIP("10.0.0.1"))), + HaveField("Spec.LoadBalancerTargets", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.PrefixFrom(loadBalancer.Spec.IPs[0].IP.Addr, 32)), + }), + )), + HaveField("Spec.NodeName", Equal(&metalnetNode.Name)), + HaveField("Spec.FirewallRules", ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionIngress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionAccept), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("192.168.1.0/24")), + })), + "DestinationPrefix": BeNil(), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": PointTo(Equal(int32(8080))), + "EndSrcPort": Equal(int32(8090)), + "DstPort": BeNil(), + "EndDstPort": BeEquivalentTo(0), + })), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionIngress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionDeny), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("192.168.2.100/32")), + })), + "DestinationPrefix": BeNil(), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": PointTo(Equal(int32(8080))), + "EndSrcPort": Equal(int32(8090)), + "DstPort": BeNil(), + "EndDstPort": BeEquivalentTo(0), + })), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionIngress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionAccept), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("192.168.2.0/24")), + })), + "DestinationPrefix": BeNil(), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": PointTo(Equal(int32(8080))), + "EndSrcPort": Equal(int32(8090)), + "DstPort": BeNil(), + "EndDstPort": BeEquivalentTo(0), + })), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionEgress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionAccept), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": BeNil(), + "DestinationPrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("10.0.0.0/16")), + })), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": BeNil(), + "EndSrcPort": BeEquivalentTo(0), + "DstPort": PointTo(Equal(int32(8095))), + "EndDstPort": BeEquivalentTo(0), + })), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionEgress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionAccept), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": BeNil(), + "DestinationPrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("10.0.0.0/16")), + })), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": BeNil(), + "EndSrcPort": BeEquivalentTo(0), + "DstPort": PointTo(Equal(int32(9000))), + "EndDstPort": Equal(int32(9010)), + })), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionEgress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionAccept), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": BeNil(), + "DestinationPrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("192.168.178.60/32")), + })), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": BeNil(), + "EndSrcPort": BeEquivalentTo(0), + "DstPort": PointTo(Equal(int32(8095))), + "EndDstPort": BeEquivalentTo(0), + })), + })), + }), + MatchFields(IgnoreExtras, Fields{ + "FirewallRuleID": Not(BeEmpty()), + "Direction": Equal(metalnetv1alpha1.FirewallRuleDirectionEgress), + "Action": Equal(metalnetv1alpha1.FirewallRuleActionAccept), + "Priority": PointTo(Equal(int32(3000))), + "IpFamily": Equal(corev1.IPv4Protocol), + "SourcePrefix": BeNil(), + "DestinationPrefix": PointTo(MatchFields(IgnoreExtras, Fields{ + "Prefix": Equal(netip.MustParsePrefix("192.168.178.60/32")), + })), + "ProtocolMatch": PointTo(MatchFields(IgnoreExtras, Fields{ + "ProtocolType": PointTo(Equal(metalnetv1alpha1.FirewallRuleProtocolTypeTCP)), + "PortRange": PointTo(MatchFields(IgnoreExtras, Fields{ + "SrcPort": BeNil(), + "EndSrcPort": BeEquivalentTo(0), + "DstPort": PointTo(Equal(int32(9000))), + "EndDstPort": Equal(int32(9010)), + })), + })), + }), + )), + )) By("updating the metalnet network interface's status") Eventually(UpdateStatus(metalnetNic, func() {