From c2e6fab6ff586b5946bd0464f35a02021d9536ac Mon Sep 17 00:00:00 2001 From: Qian Sun Date: Tue, 7 Nov 2023 03:12:25 +0000 Subject: [PATCH] Basic implementation of network policy controller --- cmd/main.go | 28 +- pkg/controllers/common/types.go | 1 + .../networkpolicy/networkpolicy_controller.go | 203 +++++++++++ .../securitypolicy_controller.go | 17 +- pkg/nsx/services/common/types.go | 127 ++++--- pkg/nsx/services/securitypolicy/builder.go | 224 ++++++++---- .../services/securitypolicy/builder_test.go | 141 +++++++- pkg/nsx/services/securitypolicy/expand.go | 22 +- .../services/securitypolicy/expand_test.go | 5 +- pkg/nsx/services/securitypolicy/firewall.go | 339 ++++++++++++++++-- .../services/securitypolicy/firewall_test.go | 8 +- pkg/nsx/services/securitypolicy/store.go | 44 ++- pkg/nsx/services/securitypolicy/store_test.go | 74 +--- pkg/util/utils.go | 114 ++++++ pkg/util/utils_test.go | 11 + 15 files changed, 1058 insertions(+), 300 deletions(-) create mode 100644 pkg/controllers/networkpolicy/networkpolicy_controller.go diff --git a/cmd/main.go b/cmd/main.go index dc6a4f2f4..3af5a6947 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,6 +22,7 @@ import ( commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" ippool2 "github.com/vmware-tanzu/nsx-operator/pkg/controllers/ippool" namespacecontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/namespace" + networkpolicycontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/networkpolicy" "github.com/vmware-tanzu/nsx-operator/pkg/controllers/node" nsxserviceaccountcontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/nsxserviceaccount" "github.com/vmware-tanzu/nsx-operator/pkg/controllers/pod" @@ -37,7 +38,6 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/ippool" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/nsxserviceaccount" - "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/securitypolicy" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" ) @@ -78,23 +78,6 @@ func init() { } } -func StartSecurityPolicyController(mgr ctrl.Manager, commonService common.Service) { - securityReconcile := &securitypolicycontroller.SecurityPolicyReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - } - if securityService, err := securitypolicy.InitializeSecurityPolicy(commonService); err != nil { - log.Error(err, "failed to initialize securitypolicy commonService", "controller", "SecurityPolicy") - os.Exit(1) - } else { - securityReconcile.Service = securityService - } - if err := securityReconcile.Start(mgr); err != nil { - log.Error(err, "failed to create controller", "controller", "SecurityPolicy") - os.Exit(1) - } -} - func StartNSXServiceAccountController(mgr ctrl.Manager, commonService common.Service) { log.Info("starting NSXServiceAccountController") nsxServiceAccountReconcile := &nsxserviceaccountcontroller.NSXServiceAccountReconciler{ @@ -210,10 +193,15 @@ func main() { StartNamespaceController(mgr, commonService) StartVPCController(mgr, commonService) StartIPPoolController(mgr, commonService) + + if err := networkpolicycontroller.StartNetworkPolicyController(mgr, commonService); err != nil { + os.Exit(1) + } } - // Start the security policy controller. - StartSecurityPolicyController(mgr, commonService) + if err := securitypolicycontroller.StartSecurityPolicyController(mgr, commonService); err != nil { + os.Exit(1) + } // Start the NSXServiceAccount controller. if cf.EnableAntreaNSXInterworking { diff --git a/pkg/controllers/common/types.go b/pkg/controllers/common/types.go index b280edace..64b57c6ed 100644 --- a/pkg/controllers/common/types.go +++ b/pkg/controllers/common/types.go @@ -10,6 +10,7 @@ import ( const ( MetricResTypeSecurityPolicy = "securitypolicy" + MetricResTypeNetworkPolicy = "networkpolicy" MetricResTypeIPPool = "ippool" MetricResTypeNSXServiceAccount = "nsxserviceaccount" MetricResTypeSubnetPort = "subnetport" diff --git a/pkg/controllers/networkpolicy/networkpolicy_controller.go b/pkg/controllers/networkpolicy/networkpolicy_controller.go new file mode 100644 index 000000000..64ed276a2 --- /dev/null +++ b/pkg/controllers/networkpolicy/networkpolicy_controller.go @@ -0,0 +1,203 @@ +/* Copyright © 2024 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package networkpolicy + +import ( + "context" + "errors" + "runtime" + "time" + + networkingv1 "k8s.io/api/networking/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/securitypolicy" + nsxutil "github.com/vmware-tanzu/nsx-operator/pkg/nsx/util" +) + +var ( + log = logger.Log + ResultNormal = common.ResultNormal + ResultRequeue = common.ResultRequeue + ResultRequeueAfter5mins = common.ResultRequeueAfter5mins + MetricResType = common.MetricResTypeNetworkPolicy +) + +// NetworkPolicyReconciler reconciles a NetworkPolicy object +type NetworkPolicyReconciler struct { + Client client.Client + Scheme *apimachineryruntime.Scheme + Service *securitypolicy.SecurityPolicyService +} + +func updateFail(r *NetworkPolicyReconciler, c *context.Context, o *networkingv1.NetworkPolicy, e *error) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResType) +} + +func k8sClient(mgr ctrl.Manager) client.Client { + var c client.Client + if mgr != nil { + c = mgr.GetClient() + } + return c +} + +func deleteFail(r *NetworkPolicyReconciler, c *context.Context, o *networkingv1.NetworkPolicy, e *error) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResType) +} + +func updateSuccess(r *NetworkPolicyReconciler, c *context.Context, o *networkingv1.NetworkPolicy) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResType) +} + +func deleteSuccess(r *NetworkPolicyReconciler, _ *context.Context, _ *networkingv1.NetworkPolicy) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResType) +} + +func (r *NetworkPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + networkPolicy := &networkingv1.NetworkPolicy{} + log.Info("reconciling networkpolicy", "networkpolicy", req.NamespacedName) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResType) + + if err := r.Client.Get(ctx, req.NamespacedName, networkPolicy); err != nil { + log.Error(err, "unable to fetch network policy", "req", req.NamespacedName) + return ResultNormal, client.IgnoreNotFound(err) + } + + if networkPolicy.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResType) + if !controllerutil.ContainsFinalizer(networkPolicy, servicecommon.NetworkPolicyFinalizerName) { + controllerutil.AddFinalizer(networkPolicy, servicecommon.NetworkPolicyFinalizerName) + if err := r.Client.Update(ctx, networkPolicy); err != nil { + log.Error(err, "add finalizer", "networkpolicy", req.NamespacedName) + updateFail(r, &ctx, networkPolicy, &err) + return ResultRequeue, err + } + log.V(1).Info("added finalizer on networkpolicy", "networkpolicy", req.NamespacedName) + } + + if err := r.Service.CreateOrUpdateSecurityPolicy(networkPolicy); err != nil { + if errors.As(err, &nsxutil.RestrictionError{}) { + log.Error(err, err.Error(), "networkpolicy", req.NamespacedName) + updateFail(r, &ctx, networkPolicy, &err) + return ResultNormal, nil + } + log.Error(err, "create or update failed, would retry exponentially", "networkpolicy", req.NamespacedName) + updateFail(r, &ctx, networkPolicy, &err) + return ResultRequeue, err + } + updateSuccess(r, &ctx, networkPolicy) + } else { + if controllerutil.ContainsFinalizer(networkPolicy, servicecommon.NetworkPolicyFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) + if err := r.Service.DeleteSecurityPolicy(networkPolicy, false, servicecommon.ResourceTypeNetworkPolicy); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "networkpolicy", req.NamespacedName) + deleteFail(r, &ctx, networkPolicy, &err) + return ResultRequeue, err + } + controllerutil.RemoveFinalizer(networkPolicy, servicecommon.NetworkPolicyFinalizerName) + if err := r.Client.Update(ctx, networkPolicy); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "networkpolicy", req.NamespacedName) + deleteFail(r, &ctx, networkPolicy, &err) + return ResultRequeue, err + } + log.V(1).Info("removed finalizer", "networkpolicy", req.NamespacedName) + deleteSuccess(r, &ctx, networkPolicy) + } else { + // only print a message because it's not a normal case + log.Info("finalizers cannot be recognized", "networkpolicy", req.NamespacedName) + } + } + + return ResultNormal, nil +} + +func (r *NetworkPolicyReconciler) setupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1.NetworkPolicy{}). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Complete(r) +} + +// Start setup manager and launch GC +func (r *NetworkPolicyReconciler) Start(mgr ctrl.Manager) error { + err := r.setupWithManager(mgr) + if err != nil { + return err + } + + go r.GarbageCollector(make(chan bool), servicecommon.GCInterval) + return nil +} + +// GarbageCollector collect networkpolicy which has been removed from K8s. +// cancel is used to break the loop during UT +func (r *NetworkPolicyReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxPolicySet := r.Service.ListNetworkPolicyID() + if len(nsxPolicySet) == 0 { + continue + } + policyList := &networkingv1.NetworkPolicyList{} + err := r.Client.List(ctx, policyList) + if err != nil { + log.Error(err, "failed to list NetworkPolicy") + continue + } + + CRPolicySet := sets.NewString() + for _, policy := range policyList.Items { + CRPolicySet.Insert(r.Service.BuildNetworkPolicyAllowPolicyID(string(policy.UID))) + CRPolicySet.Insert(r.Service.BuildNetworkPolicyIsolationPolicyID(string(policy.UID))) + } + + for elem := range nsxPolicySet { + if CRPolicySet.Has(elem) { + continue + } + log.V(1).Info("GC collected NetworkPolicy", "ID", elem) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) + err = r.Service.DeleteSecurityPolicy(types.UID(elem), false, servicecommon.ResourceTypeNetworkPolicy) + if err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResType) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResType) + } + } + } +} + +func StartNetworkPolicyController(mgr ctrl.Manager, commonService servicecommon.Service) error { + networkPolicyReconcile := NetworkPolicyReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + networkPolicyReconcile.Service = securitypolicy.GetSecurityService(commonService) + if err := networkPolicyReconcile.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "NetworkPolicy") + return err + } + return nil +} diff --git a/pkg/controllers/securitypolicy/securitypolicy_controller.go b/pkg/controllers/securitypolicy/securitypolicy_controller.go index 0e3dfe94b..a62842155 100644 --- a/pkg/controllers/securitypolicy/securitypolicy_controller.go +++ b/pkg/controllers/securitypolicy/securitypolicy_controller.go @@ -134,7 +134,7 @@ func (r *SecurityPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque } else { if controllerutil.ContainsFinalizer(obj, servicecommon.SecurityPolicyFinalizerName) { metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) - if err := r.Service.DeleteSecurityPolicy(obj, false); err != nil { + if err := r.Service.DeleteSecurityPolicy(obj, false, servicecommon.ResourceTypeSecurityPolicy); err != nil { log.Error(err, "deletion failed, would retry exponentially", "securitypolicy", req.NamespacedName) deleteFail(r, &ctx, obj, &err) return ResultRequeue, err @@ -288,7 +288,7 @@ func (r *SecurityPolicyReconciler) GarbageCollector(cancel chan bool, timeout ti } log.V(1).Info("GC collected SecurityPolicy CR", "UID", elem) metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) - err = r.Service.DeleteSecurityPolicy(types.UID(elem), false) + err = r.Service.DeleteSecurityPolicy(types.UID(elem), false, servicecommon.ResourceTypeSecurityPolicy) if err != nil { metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResType) } else { @@ -337,3 +337,16 @@ func reconcileSecurityPolicy(client client.Client, pods []v1.Pod, q workqueue.Ra } return nil } + +func StartSecurityPolicyController(mgr ctrl.Manager, commonService servicecommon.Service) error { + securityPolicyReconcile := SecurityPolicyReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + securityPolicyReconcile.Service = securitypolicy.GetSecurityService(commonService) + if err := securityPolicyReconcile.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "SecurityPolicy") + return err + } + return nil +} diff --git a/pkg/nsx/services/common/types.go b/pkg/nsx/services/common/types.go index f8e1ad644..17f0d0385 100644 --- a/pkg/nsx/services/common/types.go +++ b/pkg/nsx/services/common/types.go @@ -15,65 +15,69 @@ import ( ) const ( - HashLength int = 8 - MaxTagLength int = 256 - MaxIdLength int = 255 - MaxNameLength int = 255 - MaxSubnetNameLength int = 80 - TagScopeNCPCluster string = "ncp/cluster" - TagScopeNCPProjectUID string = "ncp/project_uid" - TagScopeNCPVIFProjectUID string = "ncp/vif_project_uid" - TagScopeNCPPod string = "ncp/pod" - TagScopeNCPVNETInterface string = "ncp/vnet_interface" - TagScopeVersion string = "nsx-op/version" - TagScopeCluster string = "nsx-op/cluster" - TagScopeNamespace string = "nsx-op/namespace" - TagScopeNamespaceUID string = "nsx-op/namespace_uid" - TagScopeSecurityPolicyCRName string = "nsx-op/security_policy_name" - TagScopeSecurityPolicyCRUID string = "nsx-op/security_policy_uid" - TagScopeStaticRouteCRName string = "nsx-op/static_route_name" - TagScopeStaticRouteCRUID string = "nsx-op/static_route_uid" - TagScopeRuleID string = "nsx-op/rule_id" - TagScopeGoupID string = "nsx-op/group_id" - TagScopeGroupType string = "nsx-op/group_type" - TagScopeSelectorHash string = "nsx-op/selector_hash" - TagScopeNSXServiceAccountCRName string = "nsx-op/nsx_service_account_name" - TagScopeNSXServiceAccountCRUID string = "nsx-op/nsx_service_account_uid" - TagScopeNSXProjectID string = "nsx-op/nsx_project_id" - TagScopeProjectGroupShared string = "nsx-op/is_nsx_project_shared" - TagScopeVPCCRName string = "nsx-op/vpc_name" - TagScopeVPCCRUID string = "nsx-op/vpc_uid" - TagScopeSubnetPortCRName string = "nsx-op/subnetport_name" - TagScopeSubnetPortCRUID string = "nsx-op/subnetport_uid" - TagScopeIPPoolCRName string = "nsx-op/ippool_name" - TagScopeIPPoolCRUID string = "nsx-op/ippool_uid" - TagScopeIPPoolCRType string = "nsx-op/ippool_type" - TagScopeIPSubnetName string = "nsx-op/ipsubnet_name" - TagScopeVMNamespaceUID string = "nsx-op/vm_namespace_uid" - TagScopeVMNamespace string = "nsx-op/vm_namespace" - LabelDefaultSubnetSet string = "nsxoperator.vmware.com/default-subnetset-for" - LabelDefaultVMSubnetSet string = "VirtualMachine" - LabelDefaultPodSubnetSet string = "Pod" - DefaultPodSubnetSet string = "pod-default" - DefaultVMSubnetSet string = "vm-default" - TagScopeSubnetCRUID string = "nsx-op/subnet_uid" - TagScopeSubnetCRName string = "nsx-op/subnet_name" - TagScopeSubnetSetCRName string = "nsx-op/subnetset_name" - TagScopeSubnetSetCRUID string = "nsx-op/subnetset_uid" - TagValueGroupScope string = "scope" - TagValueGroupSource string = "source" - TagValueGroupDestination string = "destination" - TagValueGroupAvi string = "avi" - AnnotationVPCNetworkConfig string = "nsx.vmware.com/vpc_network_config" - AnnotationVPCName string = "nsx.vmware.com/vpc_name" - AnnotationDefaultNetworkConfig string = "nsx.vmware.com/default" - AnnotationPodMAC string = "nsx.vmware.com/mac" - AnnotationPodAttachment string = "nsx.vmware.com/attachment" - TagScopePodName string = "nsx-op/pod_name" - TagScopePodUID string = "nsx-op/pod_uid" - ValueMajorVersion string = "1" - ValueMinorVersion string = "0" - ValuePatchVersion string = "0" + HashLength int = 8 + MaxTagLength int = 256 + MaxIdLength int = 255 + MaxNameLength int = 255 + MaxSubnetNameLength int = 80 + PriorityNetworkPolicyAllowRule int = 2010 + PriorityNetworkPolicyIsolationRule int = 2090 + TagScopeNCPCluster string = "ncp/cluster" + TagScopeNCPProjectUID string = "ncp/project_uid" + TagScopeNCPVIFProjectUID string = "ncp/vif_project_uid" + TagScopeNCPPod string = "ncp/pod" + TagScopeNCPVNETInterface string = "ncp/vnet_interface" + TagScopeVersion string = "nsx-op/version" + TagScopeCluster string = "nsx-op/cluster" + TagScopeNamespace string = "nsx-op/namespace" + TagScopeNamespaceUID string = "nsx-op/namespace_uid" + TagScopeSecurityPolicyCRName string = "nsx-op/security_policy_name" + TagScopeSecurityPolicyCRUID string = "nsx-op/security_policy_uid" + TagScopeNetworkPolicyName string = "nsx-op/network_policy_name" + TagScopeNetworkPolicyUID string = "nsx-op/network_policy_uid" + TagScopeStaticRouteCRName string = "nsx-op/static_route_name" + TagScopeStaticRouteCRUID string = "nsx-op/static_route_uid" + TagScopeRuleID string = "nsx-op/rule_id" + TagScopeGoupID string = "nsx-op/group_id" + TagScopeGroupType string = "nsx-op/group_type" + TagScopeSelectorHash string = "nsx-op/selector_hash" + TagScopeNSXServiceAccountCRName string = "nsx-op/nsx_service_account_name" + TagScopeNSXServiceAccountCRUID string = "nsx-op/nsx_service_account_uid" + TagScopeNSXProjectID string = "nsx-op/nsx_project_id" + TagScopeProjectGroupShared string = "nsx-op/is_nsx_project_shared" + TagScopeVPCCRName string = "nsx-op/vpc_name" + TagScopeVPCCRUID string = "nsx-op/vpc_uid" + TagScopeSubnetPortCRName string = "nsx-op/subnetport_name" + TagScopeSubnetPortCRUID string = "nsx-op/subnetport_uid" + TagScopeIPPoolCRName string = "nsx-op/ippool_name" + TagScopeIPPoolCRUID string = "nsx-op/ippool_uid" + TagScopeIPPoolCRType string = "nsx-op/ippool_type" + TagScopeIPSubnetName string = "nsx-op/ipsubnet_name" + TagScopeVMNamespaceUID string = "nsx-op/vm_namespace_uid" + TagScopeVMNamespace string = "nsx-op/vm_namespace" + LabelDefaultSubnetSet string = "nsxoperator.vmware.com/default-subnetset-for" + LabelDefaultVMSubnetSet string = "VirtualMachine" + LabelDefaultPodSubnetSet string = "Pod" + DefaultPodSubnetSet string = "pod-default" + DefaultVMSubnetSet string = "vm-default" + TagScopeSubnetCRUID string = "nsx-op/subnet_uid" + TagScopeSubnetCRName string = "nsx-op/subnet_name" + TagScopeSubnetSetCRName string = "nsx-op/subnetset_name" + TagScopeSubnetSetCRUID string = "nsx-op/subnetset_uid" + TagValueGroupScope string = "scope" + TagValueGroupSource string = "source" + TagValueGroupDestination string = "destination" + TagValueGroupAvi string = "avi" + AnnotationVPCNetworkConfig string = "nsx.vmware.com/vpc_network_config" + AnnotationVPCName string = "nsx.vmware.com/vpc_name" + AnnotationDefaultNetworkConfig string = "nsx.vmware.com/default" + AnnotationPodMAC string = "nsx.vmware.com/mac" + AnnotationPodAttachment string = "nsx.vmware.com/attachment" + TagScopePodName string = "nsx-op/pod_name" + TagScopePodUID string = "nsx-op/pod_uid" + ValueMajorVersion string = "1" + ValueMinorVersion string = "0" + ValuePatchVersion string = "0" GCInterval = 60 * time.Second RealizeTimeout = 2 * time.Minute @@ -85,6 +89,7 @@ const ( IPPoolTypePrivate = "Private" SecurityPolicyFinalizerName = "securitypolicy.nsx.vmware.com/finalizer" + NetworkPolicyFinalizerName = "networkpolicy.nsx.vmware.com/finalizer" StaticRouteFinalizerName = "staticroute.nsx.vmware.com/finalizer" NSXServiceAccountFinalizerName = "nsxserviceaccount.nsx.vmware.com/finalizer" SubnetFinalizerName = "subnet.nsx.vmware.com/finalizer" @@ -97,6 +102,9 @@ const ( IndexKeyPathPath = "Path" IndexKeyNodeName = "IndexKeyNodeName" GCValidationInterval uint16 = 720 + + NetworkPolicyRuleSuffixIngressAllow = "-ingress-allow" + NetworkPolicyRuleSuffixEgressAllow = "-egress-allow" ) var TagValueVersion = []string{ValueMajorVersion, ValueMinorVersion, ValuePatchVersion} @@ -104,6 +112,7 @@ var TagValueVersion = []string{ValueMajorVersion, ValueMinorVersion, ValuePatchV var ( ResourceType = "resource_type" ResourceTypeSecurityPolicy = "SecurityPolicy" + ResourceTypeNetworkPolicy = "NetworkPolicy" ResourceTypeGroup = "Group" ResourceTypeRule = "Rule" ResourceTypeIPBlock = "IpAddressBlock" diff --git a/pkg/nsx/services/securitypolicy/builder.go b/pkg/nsx/services/securitypolicy/builder.go index 70a7b4df3..7aaf95c03 100644 --- a/pkg/nsx/services/securitypolicy/builder.go +++ b/pkg/nsx/services/securitypolicy/builder.go @@ -37,7 +37,7 @@ var ( Int64 = common.Int64 ) -func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.SecurityPolicy) (*model.SecurityPolicy, *[]model.Group, *[]ProjectShare, error) { +func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.SecurityPolicy, createdFor string) (*model.SecurityPolicy, *[]model.Group, *[]ProjectShare, error) { var nsxRules []model.Rule var nsxGroups []model.Group var projectShares []ProjectShare @@ -54,13 +54,18 @@ func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.Security log.V(1).Info("building the model SecurityPolicy from CR SecurityPolicy", "object", *obj) nsxSecurityPolicy := &model.SecurityPolicy{} - nsxSecurityPolicy.Id = String(fmt.Sprintf("sp_%s", obj.UID)) + if createdFor == common.ResourceTypeSecurityPolicy { + nsxSecurityPolicy.Id = String(fmt.Sprintf("sp_%s", obj.UID)) + } else { + nsxSecurityPolicy.Id = String(fmt.Sprintf("np_%s", obj.UID)) + } + nsxSecurityPolicy.DisplayName = String(fmt.Sprintf("%s-%s", obj.ObjectMeta.Namespace, obj.ObjectMeta.Name)) // TODO: confirm the sequence number: offset nsxSecurityPolicy.SequenceNumber = Int64(int64(obj.Spec.Priority)) - policyGroup, policyGroupPath, err := service.buildPolicyGroup(obj) + policyGroup, policyGroupPath, err := service.buildPolicyGroup(obj, createdFor) if err != nil { log.Error(err, "failed to build policy group", "policy", *obj) return nil, nil, nil, err @@ -73,7 +78,7 @@ func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.Security for ruleIdx, rule := range obj.Spec.Rules { // A rule containing named port may expand to multiple rules if the name maps to multiple port numbers. - expandRules, ruleGroups, shares, err := service.buildRuleAndGroups(obj, &rule, ruleIdx) + expandRules, ruleGroups, shares, err := service.buildRuleAndGroups(obj, &rule, ruleIdx, createdFor) if err != nil { log.Error(err, "failed to build rule and groups", "rule", rule, "ruleIndex", ruleIdx) return nil, nil, nil, err @@ -99,20 +104,20 @@ func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.Security } nsxSecurityPolicy.Rules = nsxRules - nsxSecurityPolicy.Tags = service.buildBasicTags(obj) + nsxSecurityPolicy.Tags = service.buildBasicTags(obj, createdFor) log.V(1).Info("built nsxSecurityPolicy", "nsxSecurityPolicy", nsxSecurityPolicy, "nsxGroups", nsxGroups) return nsxSecurityPolicy, &nsxGroups, &projectShares, nil } -func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPolicy) (*model.Group, string, error) { +func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPolicy, createdFor string) (*model.Group, string, error) { policyAppliedGroup := model.Group{} - policyAppliedGroup.Id = String(service.buildAppliedGroupID(obj, -1)) + policyAppliedGroup.Id = String(service.buildAppliedGroupID(obj, -1, createdFor)) // TODO: have a common function to generate ID and Name with parameters like prefix, suffix policyAppliedGroup.DisplayName = String(service.buildAppliedGroupName(obj, -1)) appliedTo := obj.Spec.AppliedTo - targetTags := service.buildTargetTags(obj, &appliedTo, nil, -1) + targetTags := service.buildTargetTags(obj, &appliedTo, nil, -1, createdFor) policyAppliedGroup.Tags = targetTags if len(appliedTo) == 0 { return nil, "ANY", nil @@ -155,7 +160,7 @@ func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPol return nil, "", err } - policyAppliedGroupPath, err := service.buildAppliedGroupPath(obj, -1) + policyAppliedGroupPath, err := service.buildAppliedGroupPath(obj, -1, createdFor) if err != nil { return nil, "", err } @@ -165,9 +170,9 @@ func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPol } func (service *SecurityPolicyService) buildTargetTags(obj *v1alpha1.SecurityPolicy, targets *[]v1alpha1.SecurityPolicyTarget, - rule *v1alpha1.SecurityPolicyRule, ruleIdx int, + rule *v1alpha1.SecurityPolicyRule, ruleIdx int, createdFor string, ) []model.Tag { - basicTags := service.buildBasicTags(obj) + basicTags := service.buildBasicTags(obj, createdFor) sort.Slice(*targets, func(i, j int) bool { k1, _ := json.Marshal((*targets)[i]) k2, _ := json.Marshal((*targets)[j]) @@ -206,7 +211,7 @@ func (service *SecurityPolicyService) buildTargetTags(obj *v1alpha1.SecurityPoli targetTags = append(targetTags, model.Tag{ Scope: String(common.TagScopeRuleID), - Tag: String(service.buildRuleID(obj, rule, ruleIdx)), + Tag: String(service.buildRuleID(obj, rule, ruleIdx, createdFor)), }, ) } @@ -214,7 +219,13 @@ func (service *SecurityPolicyService) buildTargetTags(obj *v1alpha1.SecurityPoli } // Todo, use the uitl basic func to generate basic tags -func (service *SecurityPolicyService) buildBasicTags(obj *v1alpha1.SecurityPolicy) []model.Tag { +func (service *SecurityPolicyService) buildBasicTags(obj *v1alpha1.SecurityPolicy, createdFor string) []model.Tag { + scopeOwnerName := common.TagScopeSecurityPolicyCRName + scopeOwnerUID := common.TagScopeSecurityPolicyCRUID + if createdFor == common.ResourceTypeNetworkPolicy { + scopeOwnerName = common.TagScopeNetworkPolicyName + scopeOwnerUID = common.TagScopeNetworkPolicyUID + } tags := []model.Tag{ { Scope: String(common.TagScopeVersion), @@ -233,11 +244,11 @@ func (service *SecurityPolicyService) buildBasicTags(obj *v1alpha1.SecurityPolic Tag: String(string(service.getNamespaceUID(obj.ObjectMeta.Namespace))), }, { - Scope: String(common.TagScopeSecurityPolicyCRName), + Scope: String(scopeOwnerName), Tag: String(obj.ObjectMeta.Name), }, { - Scope: String(common.TagScopeSecurityPolicyCRUID), + Scope: String(scopeOwnerUID), Tag: String(string(obj.UID)), }, } @@ -348,21 +359,26 @@ func (service *SecurityPolicyService) buildExpressionsMatchExpression(matchExpre } // build appliedTo group ID for both policy and rule levels. -func (service *SecurityPolicyService) buildAppliedGroupID(obj *v1alpha1.SecurityPolicy, ruleIdx int) string { +func (service *SecurityPolicyService) buildAppliedGroupID(obj *v1alpha1.SecurityPolicy, ruleIdx int, createdFor string) string { + prefix := "sp" + if createdFor == common.ResourceTypeNetworkPolicy { + prefix = "np" + } + if ruleIdx != -1 { - return fmt.Sprintf("sp_%s_%d_scope", obj.UID, ruleIdx) + return fmt.Sprintf("%s_%s_%d_scope", prefix, obj.UID, ruleIdx) } - return fmt.Sprintf("sp_%s_scope", obj.UID) + return fmt.Sprintf("%s_%s_scope", prefix, obj.UID) } // build appliedTo group path for both policy and rule levels. -func (service *SecurityPolicyService) buildAppliedGroupPath(obj *v1alpha1.SecurityPolicy, ruleIdx int) (string, error) { +func (service *SecurityPolicyService) buildAppliedGroupPath(obj *v1alpha1.SecurityPolicy, ruleIdx int, createdFor string) (string, error) { var groupID string if ruleIdx == -1 { - groupID = service.buildAppliedGroupID(obj, -1) + groupID = service.buildAppliedGroupID(obj, -1, createdFor) } else { - groupID = service.buildAppliedGroupID(obj, ruleIdx) + groupID = service.buildAppliedGroupID(obj, ruleIdx, createdFor) } if isVpcEnabled(service) { @@ -393,7 +409,7 @@ func (service *SecurityPolicyService) buildAppliedGroupName(obj *v1alpha1.Securi return fmt.Sprintf("%s-%s-scope", obj.ObjectMeta.Namespace, obj.ObjectMeta.Name) } -func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int) ([]*model.Rule, []*model.Group, []*ProjectShare, error) { +func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, createdFor string) ([]*model.Rule, []*model.Group, []*ProjectShare, error) { var ruleGroups []*model.Group var projectShares []*ProjectShare var nsxRuleAppliedGroup *model.Group @@ -412,7 +428,7 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP // Since a named port may map to multiple port numbers, then it would return multiple rules. // We use the destination port number of service entry to group the rules. - ipSetGroups, nsxRules, err := service.expandRule(obj, rule, ruleIdx) + ipSetGroups, nsxRules, err := service.expandRule(obj, rule, ruleIdx, createdFor) if err != nil { return nil, nil, nil, err } @@ -423,11 +439,7 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP for _, nsxRule := range nsxRules { if ruleDirection == "IN" { nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRuleInGroup( - obj, - rule, - nsxRule, - ruleIdx, - ) + obj, rule, nsxRule, ruleIdx, createdFor) if err != nil { return nil, nil, nil, err } @@ -439,11 +451,7 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP } } else if ruleDirection == "OUT" { nsxRuleDstGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRuleOutGroup( - obj, - rule, - nsxRule, - ruleIdx, - ) + obj, rule, nsxRule, ruleIdx, createdFor) if err != nil { return nil, nil, nil, err } @@ -458,12 +466,7 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP nsxRule.DestinationGroups = []string{nsxRuleDstGroupPath} nsxRuleAppliedGroup, nsxRuleAppliedGroupPath, err = service.buildRuleAppliedToGroup( - obj, - rule, - ruleIdx, - nsxRuleSrcGroupPath, - nsxRuleDstGroupPath, - ) + obj, rule, ruleIdx, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, createdFor) if err != nil { return nil, nil, nil, err } @@ -508,22 +511,19 @@ func (service *SecurityPolicyService) buildRuleServiceEntries(port v1alpha1.Secu return serviceEntry } -func (service *SecurityPolicyService) buildRuleAppliedToGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, nsxRuleSrcGroupPath string, nsxRuleDstGroupPath string) (*model.Group, string, error) { +func (service *SecurityPolicyService) buildRuleAppliedToGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, nsxRuleSrcGroupPath string, nsxRuleDstGroupPath string, createdFor string) (*model.Group, string, error) { var nsxRuleAppliedGroup *model.Group var nsxRuleAppliedGroupPath string var err error if len(rule.AppliedTo) > 0 { nsxRuleAppliedGroup, nsxRuleAppliedGroupPath, err = service.buildRuleAppliedGroupByRule( - obj, - rule, - ruleIdx, - ) + obj, rule, ruleIdx, createdFor) if err != nil { return nil, "", err } } else { nsxRuleAppliedGroupPath, err = service.buildRuleAppliedGroupByPolicy(obj, - nsxRuleSrcGroupPath, nsxRuleDstGroupPath) + nsxRuleSrcGroupPath, nsxRuleDstGroupPath, createdFor) if err != nil { return nil, "", err } @@ -531,14 +531,14 @@ func (service *SecurityPolicyService) buildRuleAppliedToGroup(obj *v1alpha1.Secu return nsxRuleAppliedGroup, nsxRuleAppliedGroupPath, nil } -func (service *SecurityPolicyService) buildRuleInGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int) (*model.Group, string, string, *ProjectShare, error) { +func (service *SecurityPolicyService) buildRuleInGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int, createdFor string) (*model.Group, string, string, *ProjectShare, error) { var nsxRuleSrcGroup *model.Group var nsxProjectShare *ProjectShare var nsxRuleSrcGroupPath string var nsxRuleDstGroupPath string var err error if len(rule.Sources) > 0 { - nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxProjectShare, err = service.buildRulePeerGroup(obj, rule, ruleIdx, true) + nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxProjectShare, err = service.buildRulePeerGroup(obj, rule, ruleIdx, true, createdFor) if err != nil { return nil, "", "", nil, err } @@ -554,7 +554,7 @@ func (service *SecurityPolicyService) buildRuleInGroup(obj *v1alpha1.SecurityPol return nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, nil } -func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int) (*model.Group, string, string, *ProjectShare, error) { +func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int, createdFor string) (*model.Group, string, string, *ProjectShare, error) { var nsxRuleDstGroup *model.Group var nsxProjectShare *ProjectShare var nsxRuleSrcGroupPath string @@ -564,7 +564,7 @@ func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPo nsxRuleDstGroupPath = nsxRule.DestinationGroups[0] } else { if len(rule.Destinations) > 0 { - nsxRuleDstGroup, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRulePeerGroup(obj, rule, ruleIdx, false) + nsxRuleDstGroup, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRulePeerGroup(obj, rule, ruleIdx, false, createdFor) if err != nil { return nil, "", "", nil, err } @@ -576,20 +576,41 @@ func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPo return nsxRuleDstGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, nil } -func (service *SecurityPolicyService) buildRuleID(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int) string { +func (service *SecurityPolicyService) buildRuleID(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, createdFor string) string { serializedBytes, _ := json.Marshal(rule) - return fmt.Sprintf("sp_%s_%s_%d", obj.UID, util.Sha1(string(serializedBytes)), ruleIdx) + prefix := "sp" + if createdFor == common.ResourceTypeNetworkPolicy { + prefix = "np" + } + return fmt.Sprintf("%s_%s_%s_%d", prefix, obj.UID, util.Sha1(string(serializedBytes)), ruleIdx) } -func (service *SecurityPolicyService) buildRuleName(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int) string { +func (service *SecurityPolicyService) buildRuleDisplayName(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, portIdx int, portAddressIdx int, createdFor string) (string, error) { + if createdFor == common.ResourceTypeNetworkPolicy { + // The internal security policy rule converted from network policy has its own name generated by the ingress/egress ports. + // e.g. input: internal security policy's rule name: TCP.http-UDP.1234-ingress-allow + // expand to NSX security policy rules with name TCP.http-UDP.1234.TCP.80-ingress-allow and TCP.http-UDP.1234.UDP.1234-ingress-allow + if rule.Ports == nil { + return rule.Name, nil + } + suffixes := []string{common.NetworkPolicyRuleSuffixIngressAllow, common.NetworkPolicyRuleSuffixEgressAllow} + ruleName := rule.Name + for _, suffix := range suffixes { + index := strings.Index(ruleName, suffix) + if index != -1 { + return fmt.Sprintf("%s.%s%s", ruleName[:index], service.buildRulePortString(&rule.Ports[portIdx]), suffix), nil + } + } + return "", fmt.Errorf("network policy's suffixes not matched") + } if len(rule.Name) > 0 { - return rule.Name + return fmt.Sprintf("%s-%d-%d", rule.Name, portIdx, portAddressIdx), nil } else { - return fmt.Sprintf("%s-%d", obj.ObjectMeta.Name, ruleIdx) + return fmt.Sprintf("%s-%d-%d-%d", obj.ObjectMeta.Name, ruleIdx, portIdx, portAddressIdx), nil } } -func (service *SecurityPolicyService) buildRuleAppliedGroupByPolicy(obj *v1alpha1.SecurityPolicy, nsxRuleSrcGroupPath string, nsxRuleDstGroupPath string) (string, error) { +func (service *SecurityPolicyService) buildRuleAppliedGroupByPolicy(obj *v1alpha1.SecurityPolicy, nsxRuleSrcGroupPath string, nsxRuleDstGroupPath string, createdFor string) (string, error) { var nsxRuleAppliedGroupPath string var err error if len(obj.Spec.AppliedTo) == 0 { @@ -599,7 +620,7 @@ func (service *SecurityPolicyService) buildRuleAppliedGroupByPolicy(obj *v1alpha // NSX-T manager will report error if all the rule's scope/src/dst are "ANY". // So if the rule's scope is empty while policy's not, the rule's scope also // will be set to the policy's scope to avoid this case. - nsxRuleAppliedGroupPath, err = service.buildAppliedGroupPath(obj, -1) + nsxRuleAppliedGroupPath, err = service.buildAppliedGroupPath(obj, -1, createdFor) if err != nil { return "", err } @@ -609,14 +630,14 @@ func (service *SecurityPolicyService) buildRuleAppliedGroupByPolicy(obj *v1alpha return nsxRuleAppliedGroupPath, nil } -func (service *SecurityPolicyService) buildRuleAppliedGroupByRule(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int) (*model.Group, string, error) { +func (service *SecurityPolicyService) buildRuleAppliedGroupByRule(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, createdFor string) (*model.Group, string, error) { var ruleAppliedGroupName string appliedTo := rule.AppliedTo - ruleAppliedGroupID := service.buildAppliedGroupID(obj, ruleIdx) + ruleAppliedGroupID := service.buildAppliedGroupID(obj, ruleIdx, common.ResourceTypeNetworkPolicy) ruleAppliedGroupName = service.buildAppliedGroupName(obj, ruleIdx) - targetTags := service.buildTargetTags(obj, &appliedTo, rule, ruleIdx) - ruleAppliedGroupPath, err := service.buildAppliedGroupPath(obj, ruleIdx) + targetTags := service.buildTargetTags(obj, &appliedTo, rule, ruleIdx, createdFor) + ruleAppliedGroupPath, err := service.buildAppliedGroupPath(obj, ruleIdx, createdFor) if err != nil { return nil, "", err } @@ -710,7 +731,7 @@ func (service *SecurityPolicyService) buildRulePeerGroupPath(obj *v1alpha1.Secur return fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), groupID), nil } -func (service *SecurityPolicyService) buildRulePeerGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, isSource bool) (*model.Group, string, *ProjectShare, error) { +func (service *SecurityPolicyService) buildRulePeerGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, isSource bool, createdFor string) (*model.Group, string, *ProjectShare, error) { var rulePeers []v1alpha1.SecurityPolicyPeer var ruleDirection string rulePeerGroupID := service.buildRulePeerGroupID(obj, ruleIdx, isSource) @@ -736,7 +757,7 @@ func (service *SecurityPolicyService) buildRulePeerGroup(obj *v1alpha1.SecurityP return nil, "", nil, err } - peerTags := service.buildPeerTags(obj, rule, ruleIdx, isSource, groupShared) + peerTags := service.buildPeerTags(obj, rule, ruleIdx, isSource, groupShared, createdFor) rulePeerGroup := model.Group{ Id: &rulePeerGroupID, DisplayName: &rulePeerGroupName, @@ -798,7 +819,7 @@ func (service *SecurityPolicyService) buildRulePeerGroup(obj *v1alpha1.SecurityP return nil, "", nil, err } // Build a nsx share resource in project level - share, err := service.buildProjectShare(obj, &rulePeerGroup, []string{rulePeerGroupPath}, *sharedWith) + share, err := service.buildProjectShare(obj, &rulePeerGroup, []string{rulePeerGroupPath}, *sharedWith, createdFor) if err != nil { log.Error(err, "failed to build project share", "rule group Name", rulePeerGroupName) return nil, "", nil, err @@ -815,7 +836,7 @@ func (service *SecurityPolicyService) buildRulePeerGroup(obj *v1alpha1.SecurityP // Build rule basic info, ruleIdx is the index of the rules of security policy, // portIdx is the index of rule's ports, portAddressIdx is the index // of multiple port number if one named port maps to multiple port numbers. -func (service *SecurityPolicyService) buildRuleBasicInfo(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, portIdx int, portAddressIdx int) (*model.Rule, error) { +func (service *SecurityPolicyService) buildRuleBasicInfo(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, portIdx int, portAddressIdx int, createdFor string) (*model.Rule, error) { ruleAction, err := getRuleAction(rule) if err != nil { return nil, err @@ -824,22 +845,26 @@ func (service *SecurityPolicyService) buildRuleBasicInfo(obj *v1alpha1.SecurityP if err != nil { return nil, err } + displayName, err := service.buildRuleDisplayName(obj, rule, ruleIdx, portIdx, portAddressIdx, createdFor) + if err != nil { + log.Error(err, "failed to build rule's display name", "object.UID", obj.UID, "rule", rule) + } nsxRule := model.Rule{ - Id: String(fmt.Sprintf("%s_%d_%d", service.buildRuleID(obj, rule, ruleIdx), portIdx, portAddressIdx)), - DisplayName: String(fmt.Sprintf("%s-%d-%d", service.buildRuleName(obj, rule, ruleIdx), portIdx, portAddressIdx)), + Id: String(fmt.Sprintf("%s_%d_%d", service.buildRuleID(obj, rule, ruleIdx, createdFor), portIdx, portAddressIdx)), + DisplayName: &displayName, Direction: &ruleDirection, SequenceNumber: Int64(int64(ruleIdx)), Action: &ruleAction, Services: []string{"ANY"}, - Tags: service.buildBasicTags(obj), + Tags: service.buildBasicTags(obj, createdFor), } log.V(1).Info("built rule basic info", "nsxRule", nsxRule) return &nsxRule, nil } -func (service *SecurityPolicyService) buildPeerTags(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, isSource, groupShared bool) []model.Tag { - basicTags := service.buildBasicTags(obj) +func (service *SecurityPolicyService) buildPeerTags(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, isSource, groupShared bool, createdFor string) []model.Tag { + basicTags := service.buildBasicTags(obj, createdFor) groupTypeTag := String(common.TagValueGroupDestination) peers := &rule.Destinations if isSource == true { @@ -866,7 +891,7 @@ func (service *SecurityPolicyService) buildPeerTags(obj *v1alpha1.SecurityPolicy }, { Scope: String(common.TagScopeRuleID), - Tag: String(service.buildRuleID(obj, rule, ruleIdx)), + Tag: String(service.buildRuleID(obj, rule, ruleIdx, createdFor)), }, { Scope: String(common.TagScopeSelectorHash), @@ -1590,7 +1615,15 @@ func (service *SecurityPolicyService) buildShareID(nsxProjectName, groupID strin return nsxProjectShareId } -func (service *SecurityPolicyService) buildShareTags(obj *v1alpha1.SecurityPolicy, projectId string, group *model.Group) []model.Tag { +func (service *SecurityPolicyService) buildShareTags(obj *v1alpha1.SecurityPolicy, projectId string, group *model.Group, createdFor string) []model.Tag { + var scopeOwnerName, scopeOwnerUID string + if createdFor == common.ResourceTypeSecurityPolicy { + scopeOwnerName = common.TagScopeSecurityPolicyCRName + scopeOwnerUID = common.TagScopeSecurityPolicyCRUID + } else { + scopeOwnerName = common.TagScopeNetworkPolicyName + scopeOwnerUID = common.TagScopeNetworkPolicyUID + } tags := []model.Tag{ { Scope: String(common.TagScopeVersion), @@ -1605,11 +1638,11 @@ func (service *SecurityPolicyService) buildShareTags(obj *v1alpha1.SecurityPolic Tag: String(string(projectId)), }, { - Scope: String(common.TagScopeSecurityPolicyCRName), + Scope: String(scopeOwnerName), Tag: String(obj.ObjectMeta.Name), }, { - Scope: String(common.TagScopeSecurityPolicyCRUID), + Scope: String(scopeOwnerUID), Tag: String(string(obj.UID)), }, { @@ -1660,7 +1693,7 @@ func (service *SecurityPolicyService) buildChildSharedResource(shareId string, s } func (service *SecurityPolicyService) buildProjectShare(obj *v1alpha1.SecurityPolicy, group *model.Group, - sharedGroupPath, sharedWith []string, + sharedGroupPath, sharedWith []string, createdFor string, ) (*model.Share, error) { resourceType := common.ResourceTypeShare vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) @@ -1670,7 +1703,7 @@ func (service *SecurityPolicyService) buildProjectShare(obj *v1alpha1.SecurityPo projectId := (*vpcInfo).ProjectID projectShareId := service.buildShareID(projectId, string(*group.Id)) - projectShareTags := service.buildShareTags(obj, projectId, group) + projectShareTags := service.buildShareTags(obj, projectId, group, createdFor) projectShareName := fmt.Sprintf("share-%s-group-%s", projectId, *group.DisplayName) childSharedResource, err := service.buildChildSharedResource(projectShareId, sharedGroupPath) @@ -1722,3 +1755,44 @@ func (service *SecurityPolicyService) getNamespaceUID(ns string) (nsUid types.UI namespace_uid := namespace.UID return namespace_uid } + +func (service *SecurityPolicyService) buildRulePortString(port *v1alpha1.SecurityPolicyPort) string { + protocol := string(port.Protocol) + if port.EndPort != 0 { + return fmt.Sprintf("%s.%s.%d", protocol, (port.Port).String(), port.EndPort) + } + return fmt.Sprintf("%s.%s", protocol, (port.Port).String()) + +} + +func (service *SecurityPolicyService) buildRulePortsString(ports *[]v1alpha1.SecurityPolicyPort, direction string) string { + if ports == nil || len(*ports) == 0 { + return "all" + } + portsString := "" + for idx, port := range *ports { + portString := service.buildRulePortString(&port) + if idx == 0 { + portsString = portString + } else { + portsString = portsString + "-" + portString + } + } + return util.GenerateTruncName(common.MaxNameLength, fmt.Sprintf("%s-%s", portsString, direction), "", "allow", "", "") +} + +func (service *SecurityPolicyService) BuildNetworkPolicyAllowPolicyName(uid string) string { + return fmt.Sprintf("%s-allow", uid) +} + +func (service *SecurityPolicyService) BuildNetworkPolicyAllowPolicyID(uid string) string { + return fmt.Sprintf("%s_allow", uid) +} + +func (service *SecurityPolicyService) BuildNetworkPolicyIsolationPolicyName(uid string) string { + return fmt.Sprintf("%s-isolation", uid) +} + +func (service *SecurityPolicyService) BuildNetworkPolicyIsolationPolicyID(uid string) string { + return fmt.Sprintf("%s_isolation", uid) +} diff --git a/pkg/nsx/services/securitypolicy/builder_test.go b/pkg/nsx/services/securitypolicy/builder_test.go index 953778bd0..d7399d9d6 100644 --- a/pkg/nsx/services/securitypolicy/builder_test.go +++ b/pkg/nsx/services/securitypolicy/builder_test.go @@ -9,10 +9,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/vmware/vsphere-automation-sdk-go/runtime/data" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) func TestBuildSecurityPolicy(t *testing.T) { @@ -30,11 +33,11 @@ func TestBuildSecurityPolicy(t *testing.T) { }, ) - podSelectorRule0IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[0], 0), 0, 0) - podSelectorRule1IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[1], 1), 0, 0) - vmSelectorRule0IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithVMSelector, &spWithVMSelector.Spec.Rules[0], 0), 0, 0) - vmSelectorRule1IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithVMSelector, &spWithVMSelector.Spec.Rules[1], 1), 0, 0) - vmSelectorRule2IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithVMSelector, &spWithVMSelector.Spec.Rules[2], 2), 0, 0) + podSelectorRule0IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[0], 0, common.ResourceTypeSecurityPolicy), 0, 0) + podSelectorRule1IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[1], 1, common.ResourceTypeSecurityPolicy), 0, 0) + vmSelectorRule0IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithVMSelector, &spWithVMSelector.Spec.Rules[0], 0, common.ResourceTypeSecurityPolicy), 0, 0) + vmSelectorRule1IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithVMSelector, &spWithVMSelector.Spec.Rules[1], 1, common.ResourceTypeSecurityPolicy), 0, 0) + vmSelectorRule2IDPort000 := fmt.Sprintf("%s_%d_%d", service.buildRuleID(&spWithVMSelector, &spWithVMSelector.Spec.Rules[2], 2, common.ResourceTypeSecurityPolicy), 0, 0) tests := []struct { name string @@ -140,7 +143,7 @@ func TestBuildSecurityPolicy(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - observedPolicy, _, _, _ := service.buildSecurityPolicy(tt.inputPolicy) + observedPolicy, _, _, _ := service.buildSecurityPolicy(tt.inputPolicy, common.ResourceTypeSecurityPolicy) assert.Equal(t, tt.expectedPolicy, observedPolicy) }) } @@ -170,7 +173,7 @@ func TestBuildPolicyGroup(t *testing.T) { defer patches.Reset() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - observedGroup, observedGroupPath, _ := service.buildPolicyGroup(tt.inputPolicy) + observedGroup, observedGroupPath, _ := service.buildPolicyGroup(tt.inputPolicy, common.ResourceTypeSecurityPolicy) assert.Equal(t, tt.expectedPolicyGroupID, observedGroup.Id) assert.Equal(t, tt.expectedPolicyGroupName, observedGroup.DisplayName) assert.Equal(t, tt.expectedPolicyGroupPath, observedGroupPath) @@ -179,7 +182,7 @@ func TestBuildPolicyGroup(t *testing.T) { } func TestBuildTargetTags(t *testing.T) { - ruleTagID0 := service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[0], 0) + ruleTagID0 := service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[0], 0, common.ResourceTypeSecurityPolicy) tests := []struct { name string inputPolicy *v1alpha1.SecurityPolicy @@ -256,13 +259,13 @@ func TestBuildTargetTags(t *testing.T) { defer patches.Reset() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.ElementsMatch(t, tt.expectedTags, service.buildTargetTags(tt.inputPolicy, tt.inputTargets, &tt.inputPolicy.Spec.Rules[0], tt.inputIndex)) + assert.ElementsMatch(t, tt.expectedTags, service.buildTargetTags(tt.inputPolicy, tt.inputTargets, &tt.inputPolicy.Spec.Rules[0], tt.inputIndex, common.ResourceTypeSecurityPolicy)) }) } } func TestBuildPeerTags(t *testing.T) { - ruleTagID0 := service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[0], 0) + ruleTagID0 := service.buildRuleID(&spWithPodSelector, &spWithPodSelector.Spec.Rules[0], 0, common.ResourceTypeSecurityPolicy) tests := []struct { name string inputPolicy *v1alpha1.SecurityPolicy @@ -321,7 +324,7 @@ func TestBuildPeerTags(t *testing.T) { defer patches.Reset() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.ElementsMatch(t, tt.expectedTags, service.buildPeerTags(tt.inputPolicy, &tt.inputPolicy.Spec.Rules[0], tt.inputIndex, true, false)) + assert.ElementsMatch(t, tt.expectedTags, service.buildPeerTags(tt.inputPolicy, &tt.inputPolicy.Spec.Rules[0], tt.inputIndex, true, false, common.ResourceTypeSecurityPolicy)) }) } } @@ -648,3 +651,119 @@ func TestUpdateMixedExpressionsMatchExpression(t *testing.T) { matchExpressions, matchLabels, &group.Expression, nil, nil, expressions) assert.NotEqual(t, nil, err) } + +var securityPolicyWithMultipleNormalPorts = v1alpha1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "null", + Name: "null", + }, + Spec: v1alpha1.SecurityPolicySpec{ + Rules: []v1alpha1.SecurityPolicyRule{ + v1alpha1.SecurityPolicyRule{ + Name: "TCP.80-UDP.1234.1235-ingress-allow", + Ports: []v1alpha1.SecurityPolicyPort{ + v1alpha1.SecurityPolicyPort{ + Protocol: "TCP", + Port: intstr.IntOrString{Type: intstr.Int, IntVal: 80}, + }, + v1alpha1.SecurityPolicyPort{ + Protocol: "UDP", + Port: intstr.IntOrString{Type: intstr.Int, IntVal: 1234}, + EndPort: 1235, + }, + }, + }, + }, + }, +} + +var securityPolicyWithOneNamedPort = v1alpha1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "null", + Name: "null", + }, + Spec: v1alpha1.SecurityPolicySpec{ + Rules: []v1alpha1.SecurityPolicyRule{ + v1alpha1.SecurityPolicyRule{ + Name: "TCP.http-UDP.1234.1235-ingress-allow", + Ports: []v1alpha1.SecurityPolicyPort{ + v1alpha1.SecurityPolicyPort{ + Protocol: "TCP", + Port: intstr.IntOrString{Type: intstr.String, StrVal: "http"}, + }, + v1alpha1.SecurityPolicyPort{ + Protocol: "UDP", + Port: intstr.IntOrString{Type: intstr.Int, IntVal: 1234}, + EndPort: 1235, + }, + }, + }, + }, + }, +} + +func TestBuildRulePortsString(t *testing.T) { + tests := []struct { + name string + inpurtPorts *[]v1alpha1.SecurityPolicyPort + direction string + expectedRulePortsString string + }{ + { + name: "build-string-for-multiple-ports-without-named-port", + inpurtPorts: &securityPolicyWithMultipleNormalPorts.Spec.Rules[0].Ports, + direction: "ingress", + expectedRulePortsString: "TCP.80-UDP.1234.1235-ingress-allow", + }, + { + name: "build-string-for-multiple-ports-without-one-named-port", + inpurtPorts: &securityPolicyWithOneNamedPort.Spec.Rules[0].Ports, + direction: "ingress", + expectedRulePortsString: "TCP.http-UDP.1234.1235-ingress-allow", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + observedString := service.buildRulePortsString(tt.inpurtPorts, tt.direction) + assert.Equal(t, tt.expectedRulePortsString, observedString) + }) + } +} + +func TestBuildRuleDisplayName(t *testing.T) { + tests := []struct { + name string + inputSecurityPolicy *v1alpha1.SecurityPolicy + inputRule *v1alpha1.SecurityPolicyRule + ruleIdx int + portIdx int + createdFor string + expectedRuleDisplayName string + }{ + { + name: "build-display-name-for-multiple-ports-0", + inputSecurityPolicy: &securityPolicyWithMultipleNormalPorts, + inputRule: &securityPolicyWithMultipleNormalPorts.Spec.Rules[0], + ruleIdx: 0, + portIdx: 0, + createdFor: common.ResourceTypeNetworkPolicy, + expectedRuleDisplayName: "TCP.80-UDP.1234.1235.TCP.80-ingress-allow", + }, + { + name: "build-display-name-for-multiple-ports-1", + inputSecurityPolicy: &securityPolicyWithOneNamedPort, + inputRule: &securityPolicyWithMultipleNormalPorts.Spec.Rules[0], + ruleIdx: 0, + portIdx: 1, + createdFor: common.ResourceTypeNetworkPolicy, + expectedRuleDisplayName: "TCP.80-UDP.1234.1235.UDP.1234.1235-ingress-allow", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + observedDisplayName, observedError := service.buildRuleDisplayName(tt.inputSecurityPolicy, tt.inputRule, tt.ruleIdx, tt.portIdx, 0, tt.createdFor) + assert.Equal(t, tt.expectedRuleDisplayName, observedDisplayName) + assert.Equal(t, nil, observedError) + }) + } +} diff --git a/pkg/nsx/services/securitypolicy/expand.go b/pkg/nsx/services/securitypolicy/expand.go index 82306dd5c..c4c43998c 100644 --- a/pkg/nsx/services/securitypolicy/expand.go +++ b/pkg/nsx/services/securitypolicy/expand.go @@ -21,20 +21,20 @@ import ( // When a rule contains named port, we should consider whether the rule should be expanded to // multiple rules if the port name maps to conflicted port numbers. func (service *SecurityPolicyService) expandRule(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, - ruleIdx int, + ruleIdx int, createdFor string, ) ([]*model.Group, []*model.Rule, error) { var nsxRules []*model.Rule var nsxGroups []*model.Group if len(rule.Ports) == 0 { - nsxRule, err := service.buildRuleBasicInfo(obj, rule, ruleIdx, 0, 0) + nsxRule, err := service.buildRuleBasicInfo(obj, rule, ruleIdx, 0, 0, createdFor) if err != nil { return nil, nil, err } nsxRules = append(nsxRules, nsxRule) } for portIdx, port := range rule.Ports { - nsxGroups2, nsxRules2, err := service.expandRuleByPort(obj, rule, ruleIdx, port, portIdx) + nsxGroups2, nsxRules2, err := service.expandRuleByPort(obj, rule, ruleIdx, port, portIdx, createdFor) if err != nil { return nil, nil, err } @@ -45,7 +45,7 @@ func (service *SecurityPolicyService) expandRule(obj *v1alpha1.SecurityPolicy, r } func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, - ruleIdx int, port v1alpha1.SecurityPolicyPort, portIdx int, + ruleIdx int, port v1alpha1.SecurityPolicyPort, portIdx int, createdFor string, ) ([]*model.Group, []*model.Rule, error) { var err error var startPort []nsxutil.PortAddress @@ -65,7 +65,7 @@ func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPol if err != nil { // In case there is no more valid ip set selected, so clear the stale ip set group in nsx if stale ips exist if errors.As(err, &nsxutil.NoEffectiveOption{}) { - groups := service.groupStore.GetByIndex(common.TagScopeRuleID, service.buildRuleID(obj, rule, ruleIdx)) + groups := service.groupStore.GetByIndex(common.TagScopeRuleID, service.buildRuleID(obj, rule, ruleIdx, createdFor)) var ipSetGroup model.Group for _, group := range groups { ipSetGroup = group @@ -83,7 +83,7 @@ func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPol } for portAddressIdx, portAddress := range startPort { - gs, r, err := service.expandRuleByService(obj, rule, ruleIdx, port, portIdx, portAddress, portAddressIdx) + gs, r, err := service.expandRuleByService(obj, rule, ruleIdx, port, portIdx, portAddress, portAddressIdx, createdFor) if err != nil { return nil, nil, err } @@ -94,11 +94,11 @@ func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPol } func (service *SecurityPolicyService) expandRuleByService(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, - port v1alpha1.SecurityPolicyPort, portIdx int, portAddress nsxutil.PortAddress, portAddressIdx int, + port v1alpha1.SecurityPolicyPort, portIdx int, portAddress nsxutil.PortAddress, portAddressIdx int, createdFor string, ) ([]*model.Group, *model.Rule, error) { var nsxGroups []*model.Group - nsxRule, err := service.buildRuleBasicInfo(obj, rule, ruleIdx, portIdx, portAddressIdx) + nsxRule, err := service.buildRuleBasicInfo(obj, rule, ruleIdx, portIdx, portAddressIdx, createdFor) if err != nil { return nil, nil, err } @@ -110,7 +110,7 @@ func (service *SecurityPolicyService) expandRuleByService(obj *v1alpha1.Security // If portAddress contains a list of IPs, we should build an ip set group for the rule. if len(portAddress.IPs) > 0 { - ruleIPSetGroup := service.buildRuleIPSetGroup(obj, rule, nsxRule, portAddress.IPs, ruleIdx) + ruleIPSetGroup := service.buildRuleIPSetGroup(obj, rule, nsxRule, portAddress.IPs, ruleIdx, createdFor) // In VPC network, NSGroup with IPAddressExpression type can be supported in VPC level as well. IPSetGroupPath, err := service.buildRuleIPSetGroupPath(obj, nsxRule, false) @@ -219,7 +219,7 @@ func (service *SecurityPolicyService) buildRuleIPSetGroupPath(obj *v1alpha1.Secu // Build an ip set group for NSX. func (service *SecurityPolicyService) buildRuleIPSetGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleModel *model.Rule, - ips []string, ruleIdx int, + ips []string, ruleIdx int, createdFor string, ) *model.Group { ipSetGroup := model.Group{} @@ -229,7 +229,7 @@ func (service *SecurityPolicyService) buildRuleIPSetGroup(obj *v1alpha1.Security ipSetGroup.DisplayName = &ipSetGroupName // IPSetGroup is always destination group for named port - peerTags := service.buildPeerTags(obj, rule, ruleIdx, false, false) + peerTags := service.buildPeerTags(obj, rule, ruleIdx, false, false, createdFor) ipSetGroup.Tags = peerTags addresses := data.NewListValue() diff --git a/pkg/nsx/services/securitypolicy/expand_test.go b/pkg/nsx/services/securitypolicy/expand_test.go index 6738d9fe7..e38cdd17b 100644 --- a/pkg/nsx/services/securitypolicy/expand_test.go +++ b/pkg/nsx/services/securitypolicy/expand_test.go @@ -18,6 +18,7 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" "github.com/vmware-tanzu/nsx-operator/pkg/config" "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) func TestSecurityPolicyService_buildRuleIPGroup(t *testing.T) { @@ -65,7 +66,7 @@ func TestSecurityPolicyService_buildRuleIPGroup(t *testing.T) { DisplayName: &policyGroupName, Expression: []*data.StructValue{blockExpression}, // build ipset group tags from input securitypolicy and securitypolicy rule - Tags: service.buildPeerTags(sp, &rule, 0, false, false), + Tags: service.buildPeerTags(sp, &rule, 0, false, false, common.ResourceTypeSecurityPolicy), } type args struct { @@ -81,7 +82,7 @@ func TestSecurityPolicyService_buildRuleIPGroup(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, service.buildRuleIPSetGroup(sp, &rule, tt.args.obj, tt.args.ips, 0), "buildRuleIPSetGroup(%v, %v)", + assert.Equalf(t, tt.want, service.buildRuleIPSetGroup(sp, &rule, tt.args.obj, tt.args.ips, 0, common.ResourceTypeSecurityPolicy), "buildRuleIPSetGroup(%v, %v)", tt.args.obj, tt.args.ips) }) } diff --git a/pkg/nsx/services/securitypolicy/firewall.go b/pkg/nsx/services/securitypolicy/firewall.go index 4c2345fa1..2abdd802a 100644 --- a/pkg/nsx/services/securitypolicy/firewall.go +++ b/pkg/nsx/services/securitypolicy/firewall.go @@ -1,9 +1,13 @@ package securitypolicy import ( + "fmt" + "os" "sync" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" @@ -11,6 +15,7 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" "github.com/vmware-tanzu/nsx-operator/pkg/logger" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" ) var ( @@ -37,6 +42,25 @@ type ProjectShare struct { share *model.Share } +var securityService *SecurityPolicyService +var lock = &sync.Mutex{} + +// GetSecurityService get singleton SecurityPolicyService instance, networkpolicy/securitypolicy controller share the same instance. +func GetSecurityService(service common.Service) *SecurityPolicyService { + if securityService == nil { + lock.Lock() + defer lock.Unlock() + if securityService == nil { + var err error + if securityService, err = InitializeSecurityPolicy(service); err != nil { + log.Error(err, "failed to initialize subnet commonService") + os.Exit(1) + } + } + } + return securityService +} + // InitializeSecurityPolicy sync NSX resources func InitializeSecurityPolicy(service common.Service) (*SecurityPolicyService, error) { wg := sync.WaitGroup{} @@ -48,22 +72,33 @@ func InitializeSecurityPolicy(service common.Service) (*SecurityPolicyService, e securityPolicyService := &SecurityPolicyService{Service: service} securityPolicyService.securityPolicyStore = &SecurityPolicyStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer( + keyFunc, cache.Indexers{ + common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID, + common.TagScopeNetworkPolicyUID: indexByNetworkPolicyUID, + }), BindingType: model.SecurityPolicyBindingType(), }} securityPolicyService.groupStore = &GroupStore{ResourceStore: common.ResourceStore{ Indexer: cache.NewIndexer(keyFunc, cache.Indexers{ - common.TagScopeSecurityPolicyCRUID: indexFunc, + common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID, + common.TagScopeNetworkPolicyUID: indexByNetworkPolicyUID, common.TagScopeRuleID: indexGroupFunc, }), BindingType: model.GroupBindingType(), }} securityPolicyService.ruleStore = &RuleStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{ + common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID, + common.TagScopeNetworkPolicyUID: indexByNetworkPolicyUID, + }), BindingType: model.RuleBindingType(), }} securityPolicyService.shareStore = &ShareStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{ + common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID, + common.TagScopeNetworkPolicyUID: indexByNetworkPolicyUID, + }), BindingType: model.ShareBindingType(), }} @@ -88,8 +123,188 @@ func InitializeSecurityPolicy(service common.Service) (*SecurityPolicyService, e return securityPolicyService, nil } -func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1.SecurityPolicy) error { - nsxSecurityPolicy, nsxGroups, nsxProjectShares, err := service.buildSecurityPolicy(obj) +func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj interface{}) error { + var err error + switch obj.(type) { + case *networkingv1.NetworkPolicy: + internalSecurityPolicies, err := service.convertNetworkPolicyToInternalSecurityPolicies(obj.(*networkingv1.NetworkPolicy)) + if err != nil { + return err + } + for _, internalSecurityPolicy := range internalSecurityPolicies { + err = service.createOrUpdateSecurityPolicy(internalSecurityPolicy, common.ResourceTypeNetworkPolicy) + if err != nil { + return err + } + } + case *v1alpha1.SecurityPolicy: + err = service.createOrUpdateSecurityPolicy(obj.(*v1alpha1.SecurityPolicy), common.ResourceTypeSecurityPolicy) + } + return err +} + +func (service *SecurityPolicyService) convertNetworkPolicyToInternalSecurityPolicies(networkPolicy *networkingv1.NetworkPolicy) ([]*v1alpha1.SecurityPolicy, error) { + securityPolicies := []*v1alpha1.SecurityPolicy{} + actionAllow := v1alpha1.RuleActionAllow + actionDrop := v1alpha1.RuleActionDrop + directionIn := v1alpha1.RuleDirectionIn + directionOut := v1alpha1.RuleDirectionOut + spAllow := &v1alpha1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: networkPolicy.Namespace, + Name: service.BuildNetworkPolicyAllowPolicyName(networkPolicy.Name), + UID: types.UID(service.BuildNetworkPolicyAllowPolicyID(string(networkPolicy.UID))), + }, + Spec: v1alpha1.SecurityPolicySpec{ + Priority: common.PriorityNetworkPolicyAllowRule, + AppliedTo: []v1alpha1.SecurityPolicyTarget{ + { + PodSelector: &networkPolicy.Spec.PodSelector, + }, + }, + }, + } + spIsolation := &v1alpha1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: networkPolicy.Namespace, + Name: service.BuildNetworkPolicyIsolationPolicyName(networkPolicy.Name), + UID: types.UID(service.BuildNetworkPolicyIsolationPolicyID(string(networkPolicy.UID))), + }, + Spec: v1alpha1.SecurityPolicySpec{ + Priority: common.PriorityNetworkPolicyIsolationRule, + AppliedTo: []v1alpha1.SecurityPolicyTarget{ + { + PodSelector: &networkPolicy.Spec.PodSelector, + }, + }, + }, + } + + if len(networkPolicy.Spec.Ingress) > 0 { + spIsolation.Spec.Rules = []v1alpha1.SecurityPolicyRule{ + { + Action: &actionDrop, + Direction: &directionIn, + Name: "ingress-isolation", + }, + } + for _, ingress := range networkPolicy.Spec.Ingress { + rule := &v1alpha1.SecurityPolicyRule{ + Action: &actionAllow, + Direction: &directionIn, + Sources: []v1alpha1.SecurityPolicyPeer{}, + } + for _, npPeer := range ingress.From { + spPeer, err := service.convertNetworkPolicyPeerToSecurityPolicyPeer(&npPeer) + if err != nil { + return securityPolicies, err + } + rule.Sources = append(rule.Sources, *spPeer) + } + for _, npPort := range ingress.Ports { + spPort, err := service.convertNetworkPolicyPortToSecurityPolicyPort(&npPort) + if err != nil { + return securityPolicies, err + } + rule.Ports = append(rule.Ports, *spPort) + } + rule.Name = service.buildRulePortsString(&rule.Ports, "ingress") + spAllow.Spec.Rules = append(spAllow.Spec.Rules, *rule) + } + } + securityPolicies = append(securityPolicies, spAllow, spIsolation) + + if len(networkPolicy.Spec.Egress) > 0 { + spIsolation.Spec.Rules = append(spIsolation.Spec.Rules, v1alpha1.SecurityPolicyRule{ + Action: &actionDrop, + Direction: &directionOut, + Name: "egress-isolation", + }) + for _, egress := range networkPolicy.Spec.Egress { + rule := &v1alpha1.SecurityPolicyRule{ + Action: &actionAllow, + Direction: &directionOut, + Destinations: []v1alpha1.SecurityPolicyPeer{}, + } + for _, npPeer := range egress.To { + spPeer, err := service.convertNetworkPolicyPeerToSecurityPolicyPeer(&npPeer) + if err != nil { + return securityPolicies, err + } + rule.Destinations = append(rule.Destinations, *spPeer) + } + for _, npPort := range egress.Ports { + spPort, err := service.convertNetworkPolicyPortToSecurityPolicyPort(&npPort) + if err != nil { + return securityPolicies, err + } + rule.Ports = append(rule.Ports, *spPort) + } + rule.Name = service.buildRulePortsString(&rule.Ports, "egress") + spAllow.Spec.Rules = append(spAllow.Spec.Rules, *rule) + } + } + securityPolicies = append(securityPolicies, spAllow, spIsolation) + return securityPolicies, nil +} + +func (service *SecurityPolicyService) convertNetworkPolicyPeerToSecurityPolicyPeer(npPeer *networkingv1.NetworkPolicyPeer) (*v1alpha1.SecurityPolicyPeer, error) { + if npPeer.PodSelector != nil && npPeer.NamespaceSelector == nil && npPeer.IPBlock == nil { + return &v1alpha1.SecurityPolicyPeer{ + PodSelector: npPeer.PodSelector, + }, nil + } else if npPeer.PodSelector == nil && npPeer.NamespaceSelector != nil && npPeer.IPBlock == nil { + return &v1alpha1.SecurityPolicyPeer{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{}, + }, + NamespaceSelector: npPeer.NamespaceSelector, + }, nil + } else if npPeer.PodSelector != nil && npPeer.NamespaceSelector != nil && npPeer.IPBlock == nil { + return &v1alpha1.SecurityPolicyPeer{ + PodSelector: npPeer.PodSelector, + NamespaceSelector: npPeer.NamespaceSelector, + }, nil + } else if npPeer.PodSelector == nil && npPeer.NamespaceSelector == nil && npPeer.IPBlock != nil { + var ipBlocks []v1alpha1.IPBlock + cidr := npPeer.IPBlock.CIDR + if npPeer.IPBlock.Except == nil { + ipBlocks = append(ipBlocks, v1alpha1.IPBlock{CIDR: cidr}) + } else { + ranges, err := util.GetCIDRRangesWithExcept(cidr, npPeer.IPBlock.Except) + if err != nil { + return nil, err + } + for _, rng := range ranges { + ipBlocks = append(ipBlocks, v1alpha1.IPBlock{CIDR: rng}) + } + } + return &v1alpha1.SecurityPolicyPeer{ + IPBlocks: ipBlocks, + }, nil + } + err := fmt.Errorf("unsupported NetworkPolicyPeer: %s", npPeer) + return nil, err +} + +func (service *SecurityPolicyService) convertNetworkPolicyPortToSecurityPolicyPort(npPort *networkingv1.NetworkPolicyPort) (*v1alpha1.SecurityPolicyPort, error) { + spPort := &v1alpha1.SecurityPolicyPort{ + Protocol: *npPort.Protocol, + Port: *npPort.Port, + } + if npPort.EndPort != nil { + spPort.EndPort = int(*npPort.EndPort) + } + return spPort, nil +} + +func (service *SecurityPolicyService) getStoresByCreatedFor(createdFor string) (*SecurityPolicyStore, *RuleStore, *GroupStore, *ShareStore) { + return service.securityPolicyStore, service.ruleStore, service.groupStore, service.shareStore +} + +func (service *SecurityPolicyService) createOrUpdateSecurityPolicy(obj *v1alpha1.SecurityPolicy, createdFor string) error { + securityPolicyStore, ruleStore, groupStore, shareStore := service.getStoresByCreatedFor(createdFor) + nsxSecurityPolicy, nsxGroups, nsxProjectShares, err := service.buildSecurityPolicy(obj, createdFor) if err != nil { log.Error(err, "failed to build SecurityPolicy") return err @@ -98,10 +313,13 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 if len(nsxSecurityPolicy.Scope) == 0 { log.Info("SecurityPolicy has empty policy-level appliedTo") } - - existingSecurityPolicy := service.securityPolicyStore.GetByKey(*nsxSecurityPolicy.Id) - existingRules := service.ruleStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(obj.UID)) - existingGroups := service.groupStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(obj.UID)) + indexScope := common.TagScopeSecurityPolicyCRUID + if createdFor == common.ResourceTypeNetworkPolicy { + indexScope = common.TagScopeNetworkPolicyUID + } + existingSecurityPolicy := securityPolicyStore.GetByKey(*nsxSecurityPolicy.Id) + existingRules := ruleStore.GetByIndex(indexScope, string(obj.UID)) + existingGroups := groupStore.GetByIndex(indexScope, string(obj.UID)) isChanged := common.CompareResource(SecurityPolicyToComparable(existingSecurityPolicy), SecurityPolicyToComparable(nsxSecurityPolicy)) changed, stale := common.CompareResources(RulesToComparable(existingRules), RulesToComparable(nsxSecurityPolicy.Rules)) @@ -159,14 +377,14 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 } // 1.Create/update project level groups - finalProjectGroups, err = service.createOrUpdateProjectGroups(obj, projectGroups) + finalProjectGroups, err = service.createOrUpdateProjectGroups(obj, projectGroups, createdFor) if err != nil { log.Error(err, "failed to create or update project level groups") return err } // 2.Create/update project shares - finalProjectShares, err = service.createOrUpdateProjectShares(obj, projectShares) + finalProjectShares, err = service.createOrUpdateProjectShares(obj, projectShares, createdFor) if err != nil { log.Error(err, "failed to create or update project share") return err @@ -186,14 +404,14 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 } if (finalProjectGroups != nil) && len(*finalProjectGroups) != 0 { - err = service.groupStore.Apply(finalProjectGroups) + err = groupStore.Apply(finalProjectGroups) if err != nil { return err } } if (finalProjectShares != nil) && len(*finalProjectShares) != 0 { - err = service.shareStore.Apply(finalProjectShares) + err = shareStore.Apply(finalProjectShares) } } else { infraSecurityPolicy, err := service.WrapHierarchySecurityPolicy(finalSecurityPolicy, finalGroups) @@ -207,22 +425,22 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 return err } - // The steps below know how to deal with CR, if there is MarkedForDelete, then delete it from store, + // The steps below know how to deal with NSX resources, if there is MarkedForDelete, then delete it from store, // otherwise add or update it to store. if isChanged { - err = service.securityPolicyStore.Apply(&finalSecurityPolicyCopy) + err = securityPolicyStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } } if !(len(changedRules) == 0 && len(staleRules) == 0) { - err = service.ruleStore.Apply(&finalSecurityPolicyCopy) + err = ruleStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } } if !(len(changedGroups) == 0 && len(staleGroups) == 0) { - err = service.groupStore.Apply(&finalGroups) + err = groupStore.Apply(&finalGroups) if err != nil { return err } @@ -231,7 +449,29 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 return nil } -func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVpcCleanup bool) error { +func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVpcCleanup bool, createdFor string) error { + var err error + switch obj.(type) { + case *networkingv1.NetworkPolicy: + internalSecurityPolicies, err := service.convertNetworkPolicyToInternalSecurityPolicies(obj.(*networkingv1.NetworkPolicy)) + if err != nil { + return err + } + for _, internalSecurityPolicy := range internalSecurityPolicies { + err = service.deleteSecurityPolicy(internalSecurityPolicy, isVpcCleanup, createdFor) + if err != nil { + return err + } + } + case *v1alpha1.SecurityPolicy: + err = service.deleteSecurityPolicy(obj, isVpcCleanup, createdFor) + case types.UID: + err = service.deleteSecurityPolicy(obj, isVpcCleanup, createdFor) + } + return err +} + +func (service *SecurityPolicyService) deleteSecurityPolicy(obj interface{}, isVpcCleanup bool, createdFor string) error { var nsxSecurityPolicy *model.SecurityPolicy var spNameSpace string var err error @@ -240,11 +480,12 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVp var projectShares *[]ProjectShare nsxProjectShares := make([]model.Share, 0) nsxProjectGroups := make([]model.Group, 0) + securityPolicyStore, ruleStore, groupStore, shareStore := service.getStoresByCreatedFor(createdFor) switch sp := obj.(type) { // This case is for normal SecurityPolicy deletion process, which means that SecurityPolicy // has corresponding nsx SecurityPolicy object case *v1alpha1.SecurityPolicy: - nsxSecurityPolicy, nsxGroups, projectShares, err = service.buildSecurityPolicy(sp) + nsxSecurityPolicy, nsxGroups, projectShares, err = service.buildSecurityPolicy(sp, createdFor) spNameSpace = sp.ObjectMeta.Namespace if err != nil { log.Error(err, "failed to build nsx SecurityPolicy in deleting") @@ -262,9 +503,13 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVp // doesn't exist in K8s any more but still has corresponding nsx SecurityPolicy object. // Hence, we use SecurityPolicy's UID here from store instead of K8s SecurityPolicy object case types.UID: - securityPolicies := service.securityPolicyStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(sp)) + indexScope := common.TagScopeSecurityPolicyCRUID + if createdFor == common.ResourceTypeNetworkPolicy { + indexScope = common.TagScopeNetworkPolicyUID + } + securityPolicies := securityPolicyStore.GetByIndex(indexScope, string(sp)) if len(securityPolicies) == 0 { - log.Info("security policy is not found in store, skip deleting it", "securityPolicyUID", sp) + log.Info("NSX security policy is not found in store, skip deleting it", "nsxSecurityPolicyUID", sp, "createdFor", createdFor) return nil } nsxSecurityPolicy = &securityPolicies[0] @@ -277,7 +522,7 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVp } } - groups := service.groupStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(sp)) + groups := groupStore.GetByIndex(indexScope, string(sp)) if len(groups) == 0 { log.Info("did not get groups with SecurityPolicy index", "UID", string(sp)) } @@ -295,7 +540,7 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVp } } } - shares := service.shareStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(sp)) + shares := shareStore.GetByIndex(indexScope, string(sp)) if len(shares) == 0 { log.Info("did not get shares with SecurityPolicy index", "UID", string(sp)) } @@ -399,15 +644,15 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVp return err } - err = service.securityPolicyStore.Apply(&finalSecurityPolicyCopy) + err = securityPolicyStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } - err = service.groupStore.Apply(nsxGroups) + err = groupStore.Apply(nsxGroups) if err != nil { return err } - err = service.ruleStore.Apply(&finalSecurityPolicyCopy) + err = ruleStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } @@ -446,10 +691,15 @@ func (service *SecurityPolicyService) createOrUpdateGroups(obj *v1alpha1.Securit } // Create a project group share to share the group with vpc in which SecurityPolicy is -func (service *SecurityPolicyService) createOrUpdateProjectShares(obj *v1alpha1.SecurityPolicy, projectShares []model.Share) (*[]model.Share, error) { +func (service *SecurityPolicyService) createOrUpdateProjectShares(obj *v1alpha1.SecurityPolicy, projectShares []model.Share, createdFor string) (*[]model.Share, error) { + _, _, _, shareStore := service.getStoresByCreatedFor(createdFor) finalShares := make([]model.Share, 0) - existingShares := service.shareStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(obj.UID)) + indexScope := common.TagScopeSecurityPolicyCRUID + if createdFor == common.ResourceTypeNetworkPolicy { + indexScope = common.TagScopeNetworkPolicyUID + } + existingShares := shareStore.GetByIndex(indexScope, string(obj.UID)) changed, stale := common.CompareResources(SharesToComparable(existingShares), SharesToComparable(projectShares)) changedShares, staleShares := ComparableToShares(changed), ComparableToShares(stale) @@ -486,10 +736,15 @@ func (service *SecurityPolicyService) createOrUpdateProjectShares(obj *v1alpha1. return &finalShares, nil } -func (service *SecurityPolicyService) createOrUpdateProjectGroups(obj *v1alpha1.SecurityPolicy, groups []model.Group) (*[]model.Group, error) { +func (service *SecurityPolicyService) createOrUpdateProjectGroups(obj *v1alpha1.SecurityPolicy, groups []model.Group, createdFor string) (*[]model.Group, error) { + _, _, groupStore, _ := service.getStoresByCreatedFor(createdFor) finalGroups := make([]model.Group, 0) - existingGroups := service.groupStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(obj.UID)) + indexScope := common.TagScopeSecurityPolicyCRUID + if createdFor == common.ResourceTypeNetworkPolicy { + indexScope = common.TagScopeNetworkPolicyUID + } + existingGroups := groupStore.GetByIndex(indexScope, string(obj.UID)) changed, stale := common.CompareResources(GroupsToComparable(existingGroups), GroupsToComparable(groups)) changedGroups, staleGroups := ComparableToGroups(changed), ComparableToGroups(stale) @@ -536,12 +791,30 @@ func (service *SecurityPolicyService) ListSecurityPolicyID() sets.String { return groupSet.Union(policySet).Union(shareSet) } +func (service *SecurityPolicyService) ListNetworkPolicyID() sets.String { + // List ListNetworkPolicyID to which groups resources are associated in group store + groupSet := service.groupStore.ListIndexFuncValues(common.TagScopeNetworkPolicyUID) + // List service to which share resources are associated in share store + shareSet := service.shareStore.ListIndexFuncValues(common.TagScopeNetworkPolicyUID) + policySet := service.securityPolicyStore.ListIndexFuncValues(common.TagScopeNetworkPolicyUID) + + return groupSet.Union(policySet).Union(shareSet) +} + func (service *SecurityPolicyService) Cleanup() error { // Delete all the security policies in store uids := service.ListSecurityPolicyID() - log.Info("cleaning up security policies", "count", len(uids)) + log.Info("cleaning up security policies created for CR", "count", len(uids)) + for uid := range uids { + err := service.DeleteSecurityPolicy(types.UID(uid), true, common.ResourceTypeSecurityPolicy) + if err != nil { + return err + } + } + uids = service.ListNetworkPolicyID() + log.Info("cleaning up security policies created for network policy", "count", len(uids)) for uid := range uids { - err := service.DeleteSecurityPolicy(types.UID(uid), true) + err := service.DeleteSecurityPolicy(types.UID(uid), true, common.ResourceTypeNetworkPolicy) if err != nil { return err } diff --git a/pkg/nsx/services/securitypolicy/firewall_test.go b/pkg/nsx/services/securitypolicy/firewall_test.go index 0af09411e..267bfe108 100644 --- a/pkg/nsx/services/securitypolicy/firewall_test.go +++ b/pkg/nsx/services/securitypolicy/firewall_test.go @@ -284,19 +284,19 @@ func TestListSecurityPolicyID(t *testing.T) { Service: common.Service{NSXClient: nil}, } service.securityPolicyStore = &SecurityPolicyStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}), BindingType: model.SecurityPolicyBindingType(), }} service.groupStore = &GroupStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}), BindingType: model.GroupBindingType(), }} service.ruleStore = &RuleStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}), BindingType: model.RuleBindingType(), }} service.shareStore = &ShareStore{ResourceStore: common.ResourceStore{ - Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}), BindingType: model.ShareBindingType(), }} diff --git a/pkg/nsx/services/securitypolicy/store.go b/pkg/nsx/services/securitypolicy/store.go index a986ff31f..ea39b2564 100644 --- a/pkg/nsx/services/securitypolicy/store.go +++ b/pkg/nsx/services/securitypolicy/store.go @@ -24,32 +24,46 @@ func keyFunc(obj interface{}) (string, error) { } } -// indexFunc is used to get index of a resource, usually, which is the UID of the CR controller reconciles, +func filterTag(tags []model.Tag, tagScope string) []string { + var res []string + for _, tag := range tags { + if *tag.Scope == tagScope { + res = append(res, *tag.Tag) + } + } + return res +} + +// indexBySecurityPolicyCRUID is used to get index of a resource, usually, which is the UID of the CR controller reconciles, // index is used to filter out resources which are related to the CR -func indexFunc(obj interface{}) ([]string, error) { - res := make([]string, 0, 5) +func indexBySecurityPolicyCRUID(obj interface{}) ([]string, error) { switch o := obj.(type) { case model.SecurityPolicy: - return filterTag(o.Tags), nil + return filterTag(o.Tags, common.TagScopeSecurityPolicyCRUID), nil case model.Group: - return filterTag(o.Tags), nil + return filterTag(o.Tags, common.TagScopeSecurityPolicyCRUID), nil case model.Rule: - return filterTag(o.Tags), nil + return filterTag(o.Tags, common.TagScopeSecurityPolicyCRUID), nil case model.Share: - return filterTag(o.Tags), nil + return filterTag(o.Tags, common.TagScopeSecurityPolicyCRUID), nil default: - return res, errors.New("indexFunc doesn't support unknown type") + return nil, errors.New("indexBySecurityPolicyCRUID doesn't support unknown type") } } -var filterTag = func(v []model.Tag) []string { - res := make([]string, 0, 5) - for _, tag := range v { - if *tag.Scope == common.TagScopeSecurityPolicyCRUID { - res = append(res, *tag.Tag) - } +func indexByNetworkPolicyUID(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.SecurityPolicy: + return filterTag(o.Tags, common.TagScopeNetworkPolicyUID), nil + case model.Group: + return filterTag(o.Tags, common.TagScopeNetworkPolicyUID), nil + case model.Rule: + return filterTag(o.Tags, common.TagScopeNetworkPolicyUID), nil + case model.Share: + return filterTag(o.Tags, common.TagScopeNetworkPolicyUID), nil + default: + return nil, errors.New("indexByNetworkPolicyUID doesn't support unknown type") } - return res } func indexGroupFunc(obj interface{}) ([]string, error) { diff --git a/pkg/nsx/services/securitypolicy/store_test.go b/pkg/nsx/services/securitypolicy/store_test.go index 49ff82500..f65b0f109 100644 --- a/pkg/nsx/services/securitypolicy/store_test.go +++ b/pkg/nsx/services/securitypolicy/store_test.go @@ -19,68 +19,6 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) -func Test_IndexFunc(t *testing.T) { - mId, mTag, mScope := "11111", "11111", "nsx-op/security_policy_uid" - m := model.Group{ - Id: &mId, - Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, - } - s := model.SecurityPolicy{ - Id: &mId, - Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, - } - r := model.Rule{ - Id: &mId, - Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, - } - t.Run("1", func(t *testing.T) { - got, _ := indexFunc(s) - if !reflect.DeepEqual(got, []string{"11111"}) { - t.Errorf("securityPolicyCRUIDScopeIndexFunc() = %v, want %v", got, model.Tag{Tag: &mTag, Scope: &mScope}) - } - }) - t.Run("2", func(t *testing.T) { - got, _ := indexFunc(m) - if !reflect.DeepEqual(got, []string{"11111"}) { - t.Errorf("securityPolicyCRUIDScopeIndexFunc() = %v, want %v", got, model.Tag{Tag: &mTag, Scope: &mScope}) - } - }) - t.Run("3", func(t *testing.T) { - got, _ := indexFunc(r) - if !reflect.DeepEqual(got, []string{"11111"}) { - t.Errorf("securityPolicyCRUIDScopeIndexFunc() = %v, want %v", got, model.Tag{Tag: &mTag, Scope: &mScope}) - } - }) -} - -func Test_filterTag(t *testing.T) { - mTag, mScope := "11111", "nsx-op/security_policy_uid" - mTag2, mScope2 := "11111", "nsx" - tags := []model.Tag{{Scope: &mScope, Tag: &mTag}} - tags2 := []model.Tag{{Scope: &mScope2, Tag: &mTag2}} - var res []string - var res2 []string - type args struct { - v []model.Tag - res []string - } - tests := []struct { - name string - args args - want []string - }{ - {"1", args{v: tags, res: res}, []string{"11111"}}, - {"1", args{v: tags2, res: res2}, []string{}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := filterTag(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("filterTag() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_KeyFunc(t *testing.T) { Id := "11111" g := model.Group{Id: &Id} @@ -129,7 +67,7 @@ func Test_InitializeRuleStore(t *testing.T) { }, }, } - ruleCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) + ruleCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}) ruleStore := &RuleStore{ResourceStore: common.ResourceStore{ Indexer: ruleCacheIndexer, BindingType: model.RuleBindingType(), @@ -180,7 +118,7 @@ func Test_InitializeGroupStore(t *testing.T) { }, }, } - groupCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) + groupCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}) groupStore := &GroupStore{ResourceStore: common.ResourceStore{ Indexer: groupCacheIndexer, BindingType: model.GroupBindingType(), @@ -231,7 +169,7 @@ func Test_InitializeSecurityPolicyStore(t *testing.T) { }, }, } - securityPolicyCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) + securityPolicyCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}) securityPolicyStore := &SecurityPolicyStore{ResourceStore: common.ResourceStore{ Indexer: securityPolicyCacheIndexer, BindingType: model.SecurityPolicyBindingType(), @@ -260,7 +198,7 @@ func Test_InitializeSecurityPolicyStore(t *testing.T) { } func TestSecurityPolicyStore_Apply(t *testing.T) { - securityPolicyCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) + securityPolicyCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}) resourceStore := common.ResourceStore{ Indexer: securityPolicyCacheIndexer, BindingType: model.SecurityPolicyBindingType(), @@ -284,7 +222,7 @@ func TestSecurityPolicyStore_Apply(t *testing.T) { } func TestRuleStore_Apply(t *testing.T) { - ruleCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) + ruleCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}) resourceStore := common.ResourceStore{ Indexer: ruleCacheIndexer, BindingType: model.RuleBindingType(), @@ -340,7 +278,7 @@ func TestRuleStore_Apply(t *testing.T) { } func TestGroupStore_Operator(t *testing.T) { - groupCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) + groupCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexBySecurityPolicyCRUID}) resourceStore := common.ResourceStore{ Indexer: groupCacheIndexer, BindingType: model.GroupBindingType(), diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 777965de6..30ab987ae 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -4,8 +4,10 @@ package util import ( + "bytes" "context" "crypto/sha1" + "encoding/binary" "errors" "fmt" "net" @@ -16,6 +18,7 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" v1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" @@ -39,6 +42,7 @@ var ( common.TagScopeCluster, common.TagScopeVersion, common.TagScopeStaticRouteCRName, common.TagScopeStaticRouteCRUID, common.TagScopeSecurityPolicyCRName, common.TagScopeSecurityPolicyCRUID, + common.TagScopeNetworkPolicyName, common.TagScopeNetworkPolicyUID, common.TagScopeSubnetCRName, common.TagScopeSubnetCRUID, common.TagScopeSubnetPortCRName, common.TagScopeSubnetPortCRUID, common.TagScopeVPCCRName, common.TagScopeVPCCRUID, @@ -217,6 +221,112 @@ func CalculateIPFromCIDRs(IPAddresses []string) (int, error) { return total, nil } +func parseCIDRRange(cidr string) (startIP, endIP net.IP, err error) { + // TODO: confirm whether the error message is enough + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return nil, nil, err + } + startIP = ipnet.IP + endIP = make(net.IP, len(startIP)) + copy(endIP, startIP) + for i := len(startIP) - 1; i >= 0; i-- { + endIP[i] = startIP[i] | ^ipnet.Mask[i] + } + return startIP, endIP, nil +} + +func calculateOffsetIP(ip net.IP, offset int) (net.IP, error) { + if len(ip) != net.IPv4len { + return nil, fmt.Errorf("invalid IP address format %s", ip) + } + ipInt := ipToUint32(ip) + ipInt += uint32(offset) + if int(ipInt) < 0 { + return nil, fmt.Errorf("resulting IP is less than 0") + } + if ipInt > 0xFFFFFFFF { + return nil, fmt.Errorf("resulting IP is greater than 255.255.255.255") + } + return uint32ToIP(ipInt), nil +} + +func ipToUint32(ip net.IP) uint32 { + ip = ip.To4() + return binary.BigEndian.Uint32(ip) +} + +func uint32ToIP(ipInt uint32) net.IP { + ip := make(net.IP, net.IPv4len) + binary.BigEndian.PutUint32(ip, ipInt) + return ip +} + +func rangesAbstractRange(ranges [][]net.IP, except []net.IP) [][]net.IP { + // Input: + // [][]net.IP{ + // []net.IP{ + // net.IP{"172.0.0.1"}, + // net.IP{"172.0.255.255"}, + // }, + // // except the following IP ranges + // []net.IP{ + // net.IP{"172.0.100.1"}, + // net.IP{"172.0.100.255"}, + // }, + // []net.IP{ + // net.IP{"172.0.101.1"}, + // net.IP{"172.0.101.255"}, + // }, + // } + // Output: + // [][]net.IP{ + // []net.IP{ + // net.IP{"172.0.0.1"}, + // net.IP{"172.0.99.255"}, + // }, + // []net.IP{ + // net.IP{"172.0.102.0"}, + // net.IP{"172.0.255.255"}, + // }, + // } + var results [][]net.IP + for _, rng := range ranges { + exceptPrev, _ := calculateOffsetIP(except[0], -1) + exceptNext, _ := calculateOffsetIP(except[1], 1) + if bytes.Compare(except[0], rng[0]) < 0 && bytes.Compare(rng[1], except[1]) < 0 { + } else if bytes.Compare(rng[0], except[0]) < 0 && bytes.Compare(except[0], rng[1]) < 0 { + results = append(results, []net.IP{rng[0], exceptPrev}, []net.IP{exceptNext, rng[1]}) + } else if bytes.Compare(rng[0], except[0]) < 0 && bytes.Compare(rng[1], except[1]) < 0 { + results = append(results, []net.IP{rng[0], exceptPrev}) + } else if bytes.Compare(except[0], rng[0]) < 0 && bytes.Compare(except[1], rng[1]) < 0 { + results = append(results, []net.IP{exceptNext, rng[1]}) + } + } + return results +} + +func GetCIDRRangesWithExcept(cidr string, excepts []string) ([]string, error) { + var calculatedRanges [][]net.IP + var resultRanges []string + mainStartIP, mainEndIP, err := parseCIDRRange(cidr) + calculatedRanges = append(calculatedRanges, []net.IP{mainStartIP, mainEndIP}) + if err != nil { + return nil, err + } + for _, except := range excepts { + exceptStartIP, exceptEndIP, err := parseCIDRRange(except) + if err != nil { + return nil, err + } + calculatedRanges = rangesAbstractRange(calculatedRanges, []net.IP{exceptStartIP, exceptEndIP}) + } + for _, rng := range calculatedRanges { + resultRanges = append(resultRanges, fmt.Sprintf("%s-%s", rng[0], rng[1])) + } + return resultRanges, nil +} + func If(condition bool, trueVal, falseVal interface{}) interface{} { if condition { return trueVal @@ -361,6 +471,10 @@ func BuildBasicTags(cluster string, obj interface{}, namespaceID types.UID) []mo tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) tags = append(tags, model.Tag{Scope: String(common.TagScopeSecurityPolicyCRName), Tag: String(i.ObjectMeta.Name)}) tags = append(tags, model.Tag{Scope: String(common.TagScopeSecurityPolicyCRUID), Tag: String(string(i.UID))}) + case *networkingv1.NetworkPolicy: + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeNetworkPolicyName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeNetworkPolicyUID), Tag: String(string(i.UID))}) case *v1alpha1.Subnet: tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetCRName), Tag: String(i.ObjectMeta.Name)}) tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetCRUID), Tag: String(string(i.UID))}) diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index 4fba02515..0c90eeb4e 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -440,6 +440,17 @@ func TestGenerateTruncName(t *testing.T) { }, want: "sp-1234-456", }, + { + name: "test-only-name", + args: args{ + limit: 255, + res_name: "1234-456", + prefix: "", + suffix: "", + project: "", + }, + want: "1234-456", + }, { name: "test-suffix", args: args{