diff --git a/src/operator/api/v1alpha3/clientintents_types.go b/src/operator/api/v1alpha3/clientintents_types.go index 5e0410330..524793b75 100644 --- a/src/operator/api/v1alpha3/clientintents_types.go +++ b/src/operator/api/v1alpha3/clientintents_types.go @@ -38,6 +38,7 @@ const ( OtterizeAccessLabelPrefix = "intents.otterize.com/access" OtterizeServiceAccessLabelPrefix = "intents.otterize.com/svc-access" OtterizeAccessLabelKey = "intents.otterize.com/access-%s" + OtterizeExternalAccessLabelKey = "intents.otterize.com/external-access-%s" OtterizeSvcAccessLabelKey = "intents.otterize.com/svc-access-%s" OtterizeClientLabelKey = "intents.otterize.com/client" OtterizeServiceLabelKey = "intents.otterize.com/service" diff --git a/src/operator/api/v1alpha3/otterize_labels.go b/src/operator/api/v1alpha3/otterize_labels.go index 6b1a40483..ed4242b08 100644 --- a/src/operator/api/v1alpha3/otterize_labels.go +++ b/src/operator/api/v1alpha3/otterize_labels.go @@ -30,6 +30,15 @@ func IsMissingOtterizeAccessLabels(pod *v1.Pod, otterizeAccessLabels map[string] return false } +func IsMissingOtterizeExternalAccessLabels(pod *v1.Pod) bool { + if pod.Labels == nil { + return true + } + + _, found := pod.Labels[OtterizeExternalAccessLabelKey] + return !found +} + // UpdateOtterizeAccessLabels updates a pod's labels with Otterize labels representing their intents // The pod is also labeled with "otterize-client=" to mark it as having intents or being the client-side of an egress netpol func UpdateOtterizeAccessLabels(pod *v1.Pod, serviceIdentity serviceidentity.ServiceIdentity, otterizeAccessLabels map[string]string) *v1.Pod { diff --git a/src/operator/controllers/external_traffic/network_policy.go b/src/operator/controllers/external_traffic/network_policy.go index 9b073b5f7..5df6f2922 100644 --- a/src/operator/controllers/external_traffic/network_policy.go +++ b/src/operator/controllers/external_traffic/network_policy.go @@ -42,21 +42,23 @@ type NetworkPolicyHandler struct { client client.Client scheme *runtime.Scheme injectablerecorder.InjectableRecorder - allowExternalTraffic allowexternaltraffic.Enum + allowExternalTraffic allowexternaltraffic.Enum + ingressControllerIdentities []serviceidentity.ServiceIdentity } func NewNetworkPolicyHandler( client client.Client, scheme *runtime.Scheme, allowExternalTraffic allowexternaltraffic.Enum, + ingressControllerIdentities []serviceidentity.ServiceIdentity, ) *NetworkPolicyHandler { - return &NetworkPolicyHandler{client: client, scheme: scheme, allowExternalTraffic: allowExternalTraffic} + return &NetworkPolicyHandler{client: client, scheme: scheme, allowExternalTraffic: allowExternalTraffic, ingressControllerIdentities: ingressControllerIdentities} } func (r *NetworkPolicyHandler) createOrUpdateNetworkPolicy( ctx context.Context, endpoints *corev1.Endpoints, owner *corev1.Service, otterizeServiceName string, selector metav1.LabelSelector, ingressList *v1.IngressList, successMsg string) error { policyName := r.formatPolicyName(endpoints.Name) - newPolicy := buildNetworkPolicyObjectForEndpoints(endpoints, otterizeServiceName, selector, ingressList, policyName) + newPolicy := r.buildNetworkPolicyObjectForEndpoints(endpoints, owner, otterizeServiceName, selector, ingressList, policyName) err := controllerutil.SetOwnerReference(owner, newPolicy, r.scheme) if err != nil { return errors.Wrap(err) @@ -112,9 +114,8 @@ func (r *NetworkPolicyHandler) arePoliciesEqual(existingPolicy *v1.NetworkPolicy reflect.DeepEqual(existingPolicy.OwnerReferences, newPolicy.OwnerReferences) } -func buildNetworkPolicyObjectForEndpoints( - endpoints *corev1.Endpoints, otterizeServiceName string, selector metav1.LabelSelector, ingressList *v1.IngressList, policyName string) *v1.NetworkPolicy { - serviceSpecCopy := endpoints.Subsets +func (r *NetworkPolicyHandler) buildNetworkPolicyObjectForEndpoints( + endpoints *corev1.Endpoints, svc *corev1.Service, otterizeServiceName string, selector metav1.LabelSelector, ingressList *v1.IngressList, policyName string) *v1.NetworkPolicy { annotations := map[string]string{ v1alpha3.OtterizeCreatedForServiceAnnotation: endpoints.GetName(), @@ -126,6 +127,26 @@ func buildNetworkPolicyObjectForEndpoints( }), ",") } + rule := v1.NetworkPolicyIngressRule{} + // Only limit netpol if there is an ingress controller restriction configured AND the service is not directly exposed. + if len(r.ingressControllerIdentities) != 0 && svc.Spec.Type == corev1.ServiceTypeClusterIP { + for _, ingressController := range r.ingressControllerIdentities { + rule.From = append(rule.From, v1.NetworkPolicyPeer{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + v1alpha3.OtterizeServiceLabelKey: ingressController.GetFormattedOtterizeIdentityWithoutKind(), + v1alpha3.OtterizeOwnerKindLabelKey: ingressController.Kind, + }, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + v1alpha3.KubernetesStandardNamespaceNameLabelKey: ingressController.Namespace, + }, + }, + }) + } + } + netpol := &v1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: policyName, @@ -139,12 +160,12 @@ func buildNetworkPolicyObjectForEndpoints( PolicyTypes: []v1.PolicyType{v1.PolicyTypeIngress}, PodSelector: selector, Ingress: []v1.NetworkPolicyIngressRule{ - {}, + rule, }, }, } - for _, subsets := range serviceSpecCopy { + for _, subsets := range endpoints.Subsets { for _, port := range subsets.Ports { netpolPort := v1.NetworkPolicyPort{ Port: lo.ToPtr(intstr.FromInt(int(port.Port))), diff --git a/src/operator/controllers/external_traffic/network_policy_test.go b/src/operator/controllers/external_traffic/network_policy_test.go index bf7cc9670..7d1d8f893 100644 --- a/src/operator/controllers/external_traffic/network_policy_test.go +++ b/src/operator/controllers/external_traffic/network_policy_test.go @@ -4,6 +4,7 @@ import ( "context" otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" "github.com/otterize/intents-operator/src/shared/operatorconfig/allowexternaltraffic" + "github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity" "github.com/otterize/intents-operator/src/shared/testbase" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" @@ -21,7 +22,7 @@ type NetworkPolicyHandlerTestSuite struct { func (s *NetworkPolicyHandlerTestSuite) SetupTest() { s.MocksSuiteBase.SetupTest() - s.handler = NewNetworkPolicyHandler(s.Client, &runtime.Scheme{}, allowexternaltraffic.IfBlockedByOtterize) + s.handler = NewNetworkPolicyHandler(s.Client, &runtime.Scheme{}, allowexternaltraffic.IfBlockedByOtterize, make([]serviceidentity.ServiceIdentity, 0)) } func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleBeforeAccessPolicyRemoval_createWhenNoIntentsEnabled_doNothing() { diff --git a/src/operator/controllers/external_traffic/network_policy_uploader.go b/src/operator/controllers/external_traffic/network_policy_uploader.go deleted file mode 100644 index 29cdd417f..000000000 --- a/src/operator/controllers/external_traffic/network_policy_uploader.go +++ /dev/null @@ -1,155 +0,0 @@ -package external_traffic - -import ( - "context" - "fmt" - "github.com/otterize/intents-operator/src/operator/api/v1alpha3" - "github.com/otterize/intents-operator/src/shared/errors" - "github.com/otterize/intents-operator/src/shared/injectablerecorder" - "github.com/otterize/intents-operator/src/shared/operator_cloud_client" - "github.com/otterize/intents-operator/src/shared/otterizecloud/graphqlclient" - "github.com/otterize/intents-operator/src/shared/serviceidresolver" - "github.com/samber/lo" - "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/networking/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - 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/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "time" -) - -const ( - listPodsForPolicyRetryDelay = 5 * time.Second -) - -type NetworkPolicyUploaderReconciler struct { - client.Client - Scheme *runtime.Scheme - serviceIdResolver *serviceidresolver.Resolver - otterizeClient operator_cloud_client.CloudClient - injectablerecorder.InjectableRecorder -} - -func NewNetworkPolicyUploaderReconciler( - client client.Client, - scheme *runtime.Scheme, - otterizeClient operator_cloud_client.CloudClient, -) *NetworkPolicyUploaderReconciler { - return &NetworkPolicyUploaderReconciler{ - Client: client, - Scheme: scheme, - serviceIdResolver: serviceidresolver.NewResolver(client), - otterizeClient: otterizeClient, - } -} - -func (r *NetworkPolicyUploaderReconciler) SetupWithManager(mgr ctrl.Manager) error { - recorder := mgr.GetEventRecorderFor("intents-operator") - r.InjectRecorder(recorder) - - return ctrl.NewControllerManagedBy(mgr). - For(&v1.NetworkPolicy{}). - WithOptions(controller.Options{RecoverPanic: lo.ToPtr(true)}). - WithEventFilter(filterOtterizeNetworkPolicy()). - Complete(r) -} - -func (r *NetworkPolicyUploaderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logrus.WithField("policy", req.NamespacedName.String()).Debug("Reconcile Otterize NetworkPolicy") - - netpol := &v1.NetworkPolicy{} - err := r.Get(ctx, req.NamespacedName, netpol) - if k8serrors.IsNotFound(err) { - logrus.WithField("policy", req.NamespacedName.String()).Debug("NetPol was deleted") - return ctrl.Result{}, nil - } - if err != nil { - return ctrl.Result{}, errors.Wrap(err) - } - - selector, err := metav1.LabelSelectorAsSelector(&netpol.Spec.PodSelector) - if err != nil { - return ctrl.Result{}, errors.Wrap(err) - } - - var podList corev1.PodList - err = r.List( - ctx, &podList, - &client.MatchingLabelsSelector{Selector: selector}, - &client.ListOptions{Namespace: netpol.Namespace}) - if err != nil { - logrus.WithError(err).Errorf("error when reading podlist") - return ctrl.Result{}, nil - } - - if len(podList.Items) == 0 { - logrus. - WithField("policy", req.NamespacedName.String()). - Debug("Failed to resolve any pods, will retry") - return ctrl.Result{RequeueAfter: listPodsForPolicyRetryDelay}, nil - } - - var inputs []graphqlclient.NetworkPolicyInput - - for _, pod := range podList.Items { - serviceId, err := r.serviceIdResolver.ResolvePodToServiceIdentity(ctx, &pod) - if err != nil { - return ctrl.Result{}, errors.Wrap(err) - } - - logrus. - WithField("pod", fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)). - WithField("service", serviceId.Name). - Debug("matching pod to otterize service") - - inputs = append(inputs, graphqlclient.NetworkPolicyInput{ - Namespace: req.Namespace, - Name: req.Name, - ServerName: serviceId.Name, - ExternalNetworkTrafficPolicy: true, - }) - } - - err = r.otterizeClient.ReportNetworkPolicies(ctx, req.Namespace, inputs) - if err != nil { - logrus.WithError(err). - WithField("namespace", req.Namespace). - Error("failed reporting network policies") - return ctrl.Result{}, errors.Wrap(err) - } - - return ctrl.Result{}, nil -} - -func filterOtterizeNetworkPolicy() predicate.Predicate { - return predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - labels := e.Object.GetLabels() - _, isExternalTrafficPolicy := labels[v1alpha3.OtterizeNetworkPolicyExternalTraffic] - - return isExternalTrafficPolicy - }, - UpdateFunc: func(e event.UpdateEvent) bool { - labels := e.ObjectNew.GetLabels() - _, isExternalTrafficPolicy := labels[v1alpha3.OtterizeNetworkPolicyExternalTraffic] - - return isExternalTrafficPolicy - }, - DeleteFunc: func(e event.DeleteEvent) bool { - if e.DeleteStateUnknown { - return false - } - - labels := e.Object.GetLabels() - _, isExternalTrafficPolicy := labels[v1alpha3.OtterizeNetworkPolicyExternalTraffic] - - return isExternalTrafficPolicy - }, - } -} diff --git a/src/operator/controllers/external_traffic/network_policy_uploader_test.go b/src/operator/controllers/external_traffic/network_policy_uploader_test.go deleted file mode 100644 index 1d20d1f03..000000000 --- a/src/operator/controllers/external_traffic/network_policy_uploader_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package external_traffic - -import ( - "context" - "github.com/otterize/intents-operator/src/operator/api/v1alpha3" - "github.com/otterize/intents-operator/src/shared/otterizecloud/graphqlclient" - otterizecloudmocks "github.com/otterize/intents-operator/src/shared/otterizecloud/mocks" - "github.com/otterize/intents-operator/src/shared/testbase" - "github.com/stretchr/testify/suite" - "go.uber.org/mock/gomock" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "testing" -) - -const ( - testNamespace = "test-namespace" -) - -type NetworkPolicyReconcilerTestSuite struct { - testbase.MocksSuiteBase - reconciler *NetworkPolicyUploaderReconciler - cloudClient *otterizecloudmocks.MockCloudClient -} - -func (s *NetworkPolicyReconcilerTestSuite) SetupTest() { - s.MocksSuiteBase.SetupTest() - - controller := gomock.NewController(s.T()) - s.cloudClient = otterizecloudmocks.NewMockCloudClient(controller) - s.reconciler = NewNetworkPolicyUploaderReconciler( - s.Client, - &runtime.Scheme{}, - s.cloudClient, - ) - - s.reconciler.Recorder = s.Recorder -} - -func (s *NetworkPolicyReconcilerTestSuite) TearDownTest() { - s.cloudClient = nil - s.reconciler = nil - s.MocksSuiteBase.TearDownTest() -} - -func (s *NetworkPolicyReconcilerTestSuite) TestUploadNetworkPolicy() { - accessNetworkPolicy := &v1.NetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "external-access-to-client-A", - Namespace: testNamespace, - Labels: map[string]string{ - v1alpha3.OtterizeNetworkPolicyExternalTraffic: "client-A", - }, - }, - Spec: v1.NetworkPolicySpec{ - PolicyTypes: []v1.PolicyType{v1.PolicyTypeIngress}, - PodSelector: metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: v1alpha3.OtterizeServiceLabelKey, - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - Ingress: []v1.NetworkPolicyIngressRule{ - {}, - }, - }, - } - - emptyNetworkPolicy := v1.NetworkPolicy{} - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: testNamespace, - Name: accessNetworkPolicy.Name, - }, - } - - s.Client.EXPECT().Get(gomock.Any(), req.NamespacedName, gomock.Eq(&emptyNetworkPolicy)).DoAndReturn( - func(ctx context.Context, namespacedName types.NamespacedName, networkPolicy *v1.NetworkPolicy, _ ...client.GetOption) error { - accessNetworkPolicy.DeepCopyInto(networkPolicy) - return nil - }) - - podList := &corev1.PodList{} - selector, err := metav1.LabelSelectorAsSelector(&accessNetworkPolicy.Spec.PodSelector) - s.Require().NoError(err) - - s.Client.EXPECT().List(gomock.Any(), - gomock.Eq(podList), - &client.MatchingLabelsSelector{Selector: selector}, - &client.ListOptions{Namespace: testNamespace}).DoAndReturn( - func(ctx context.Context, podList *corev1.PodList, listOptions ...client.ListOption) error { - podList.Items = []corev1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-1", - Namespace: testNamespace, - Annotations: map[string]string{ - "intents.otterize.com/service-name": "client-A", - }, - }, - }, - } - return nil - }) - - uploadedNetworkPolicy := graphqlclient.NetworkPolicyInput{ - Namespace: testNamespace, - Name: accessNetworkPolicy.Name, - ServerName: "client-A", - ExternalNetworkTrafficPolicy: true, - } - - s.cloudClient.EXPECT().ReportNetworkPolicies(gomock.Any(), testNamespace, []graphqlclient.NetworkPolicyInput{uploadedNetworkPolicy}).Return(nil) - - res, err := s.reconciler.Reconcile(context.Background(), req) - s.Require().NoError(err) - s.Require().Empty(res) -} - -func (s *NetworkPolicyReconcilerTestSuite) TestNoUploadIfNoPods() { - accessNetworkPolicy := &v1.NetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "external-access-to-client-A", - Namespace: testNamespace, - Labels: map[string]string{ - v1alpha3.OtterizeNetworkPolicyExternalTraffic: "client-A", - }, - }, - Spec: v1.NetworkPolicySpec{ - PolicyTypes: []v1.PolicyType{v1.PolicyTypeIngress}, - PodSelector: metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: v1alpha3.OtterizeServiceLabelKey, - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - Ingress: []v1.NetworkPolicyIngressRule{ - {}, - }, - }, - } - - emptyNetworkPolicy := v1.NetworkPolicy{} - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: testNamespace, - Name: accessNetworkPolicy.Name, - }, - } - - s.Client.EXPECT().Get(gomock.Any(), req.NamespacedName, gomock.Eq(&emptyNetworkPolicy)).DoAndReturn( - func(ctx context.Context, namespacedName types.NamespacedName, networkPolicy *v1.NetworkPolicy, _ ...client.GetOption) error { - accessNetworkPolicy.DeepCopyInto(networkPolicy) - return nil - }) - - podList := &corev1.PodList{} - selector, err := metav1.LabelSelectorAsSelector(&accessNetworkPolicy.Spec.PodSelector) - s.Require().NoError(err) - - s.Client.EXPECT().List(gomock.Any(), - gomock.Eq(podList), - &client.MatchingLabelsSelector{Selector: selector}, - &client.ListOptions{Namespace: testNamespace}).DoAndReturn( - func(ctx context.Context, podList *corev1.PodList, listOptions ...client.ListOption) error { - podList.Items = []corev1.Pod{} - return nil - }) - - // Expect no calls to cloud client - - res, err := s.reconciler.Reconcile(context.Background(), req) - s.Require().NoError(err) - s.Require().Equal(reconcile.Result{RequeueAfter: listPodsForPolicyRetryDelay}, res) -} - -func TestNetworkPolicyReconcilerSuite(t *testing.T) { - suite.Run(t, new(NetworkPolicyReconcilerTestSuite)) -} diff --git a/src/operator/controllers/external_traffic/service_uploader_test.go b/src/operator/controllers/external_traffic/service_uploader_test.go index 90ad000d6..7e4eeb9b4 100644 --- a/src/operator/controllers/external_traffic/service_uploader_test.go +++ b/src/operator/controllers/external_traffic/service_uploader_test.go @@ -17,6 +17,8 @@ import ( "time" ) +const testNamespace = "test-namespace" + type ServiceUploaderTestSuite struct { testbase.MocksSuiteBase serviceUploader ServiceUploader diff --git a/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_test.go b/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_test.go index 6c801e23d..229caa30c 100644 --- a/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_test.go +++ b/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_test.go @@ -75,7 +75,7 @@ func (s *ExternalNetworkPolicyReconcilerTestSuite) SetupTest() { testName := s.T().Name() isShadowMode := strings.Contains(testName, "ShadowMode") defaultActive := !isShadowMode - netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.IfBlockedByOtterize) + netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.IfBlockedByOtterize, make([]serviceidentity.ServiceIdentity, 0)) s.defaultDenyReconciler = protected_service_reconcilers.NewDefaultDenyReconciler(s.Mgr.GetClient(), netpolHandler, true) netpolReconciler := networkpolicy.NewReconciler(s.Mgr.GetClient(), s.TestEnv.Scheme, netpolHandler, []string{}, goset.NewSet[string](), true, defaultActive, []networkpolicy.IngressRuleBuilder{builders.NewIngressNetpolBuilder(), builders.NewPortNetworkPolicyReconciler(s.Mgr.GetClient())}, nil) serviceIdResolver := serviceidresolver.NewResolver(s.Mgr.GetClient()) @@ -801,7 +801,7 @@ func (s *ExternalNetworkPolicyReconcilerTestSuite) TestEndpointsReconcilerNetwor s.AddNodePortService(nodePortServiceName, podIps, podLabels) - netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Off) + netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Off, make([]serviceidentity.ServiceIdentity, 0)) endpointReconcilerWithEnforcementDisabled := external_traffic.NewEndpointsReconciler(s.Mgr.GetClient(), netpolHandler) recorder := record.NewFakeRecorder(10) endpointReconcilerWithEnforcementDisabled.InjectRecorder(recorder) diff --git a/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_ingress_controllers_configured_test.go b/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_ingress_controllers_configured_test.go new file mode 100644 index 000000000..6519dbef7 --- /dev/null +++ b/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_ingress_controllers_configured_test.go @@ -0,0 +1,912 @@ +package external_traffic_network_policy + +import ( + "context" + "fmt" + "github.com/amit7itz/goset" + "github.com/google/uuid" + otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" + "github.com/otterize/intents-operator/src/operator/controllers" + "github.com/otterize/intents-operator/src/operator/controllers/external_traffic" + "github.com/otterize/intents-operator/src/operator/controllers/intents_reconcilers" + mocks "github.com/otterize/intents-operator/src/operator/controllers/intents_reconcilers/mocks" + "github.com/otterize/intents-operator/src/operator/controllers/intents_reconcilers/networkpolicy" + "github.com/otterize/intents-operator/src/operator/controllers/intents_reconcilers/networkpolicy/builders" + "github.com/otterize/intents-operator/src/operator/controllers/pod_reconcilers" + podreconcilersmocks "github.com/otterize/intents-operator/src/operator/controllers/pod_reconcilers/mocks" + "github.com/otterize/intents-operator/src/operator/controllers/protected_service_reconcilers" + "github.com/otterize/intents-operator/src/operator/effectivepolicy" + "github.com/otterize/intents-operator/src/shared/operatorconfig/allowexternaltraffic" + "github.com/otterize/intents-operator/src/shared/serviceidresolver" + "github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity" + "github.com/otterize/intents-operator/src/shared/testbase" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" + istiosecurityscheme "istio.io/client-go/pkg/apis/security/v1beta1" + v1 "k8s.io/api/networking/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "strings" + "testing" +) + +const ingressControllerName = "ingress-controller" +const ingressControllerNamespace = "ingress-nginx" +const ingressControllerKind = "Deployment" +const ingressControllerHashedName = "ingress-controller-ingress-nginx-53b476" + +type ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite struct { + testbase.ControllerManagerTestSuiteBase + IngressReconciler *external_traffic.IngressReconciler + endpointReconciler external_traffic.EndpointsReconciler + EffectivePolicyIntentsReconciler *intents_reconcilers.ServiceEffectivePolicyIntentsReconciler + podWatcher *pod_reconcilers.PodWatcher + defaultDenyReconciler *protected_service_reconcilers.DefaultDenyReconciler +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) SetupSuite() { + s.TestEnv = &envtest.Environment{} + var err error + s.TestEnv.CRDDirectoryPaths = []string{filepath.Join("..", "..", "..", "config", "crd")} + + s.RestConfig, err = s.TestEnv.Start() + s.Require().NoError(err) + s.Require().NotNil(s.RestConfig) + + s.K8sDirectClient, err = kubernetes.NewForConfig(s.RestConfig) + s.Require().NoError(err) + s.Require().NotNil(s.K8sDirectClient) + + utilruntime.Must(apiextensionsv1.AddToScheme(s.TestEnv.Scheme)) + utilruntime.Must(clientgoscheme.AddToScheme(s.TestEnv.Scheme)) + utilruntime.Must(istiosecurityscheme.AddToScheme(s.TestEnv.Scheme)) + utilruntime.Must(otterizev1alpha3.AddToScheme(s.TestEnv.Scheme)) + utilruntime.Must(otterizev1alpha3.AddToScheme(s.TestEnv.Scheme)) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) SetupTest() { + s.ControllerManagerTestSuiteBase.SetupTest() + + recorder := s.Mgr.GetEventRecorderFor("intents-operator") + testName := s.T().Name() + isShadowMode := strings.Contains(testName, "ShadowMode") + defaultActive := !isShadowMode + netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.IfBlockedByOtterize, []serviceidentity.ServiceIdentity{ + { + Kind: "Deployment", + Namespace: ingressControllerNamespace, + Name: ingressControllerName, + }, + }) + s.defaultDenyReconciler = protected_service_reconcilers.NewDefaultDenyReconciler(s.Mgr.GetClient(), netpolHandler, true) + netpolReconciler := networkpolicy.NewReconciler(s.Mgr.GetClient(), s.TestEnv.Scheme, netpolHandler, []string{}, goset.NewSet[string](), true, defaultActive, []networkpolicy.IngressRuleBuilder{builders.NewIngressNetpolBuilder(), builders.NewPortNetworkPolicyReconciler(s.Mgr.GetClient())}, nil) + serviceIdResolver := serviceidresolver.NewResolver(s.Mgr.GetClient()) + epReconciler := effectivepolicy.NewGroupReconciler(s.Mgr.GetClient(), s.TestEnv.Scheme, serviceIdResolver, netpolReconciler) + s.EffectivePolicyIntentsReconciler = intents_reconcilers.NewServiceEffectiveIntentsReconciler(s.Mgr.GetClient(), s.TestEnv.Scheme, epReconciler) + s.Require().NoError((&controllers.IntentsReconciler{}).InitIntentsServerIndices(s.Mgr)) + s.EffectivePolicyIntentsReconciler.InjectRecorder(recorder) + + s.endpointReconciler = external_traffic.NewEndpointsReconciler(s.Mgr.GetClient(), netpolHandler) + s.endpointReconciler.InjectRecorder(recorder) + err := s.endpointReconciler.InitIngressReferencedServicesIndex(s.Mgr) + s.Require().NoError(err) + + s.IngressReconciler = external_traffic.NewIngressReconciler(s.Mgr.GetClient(), netpolHandler) + s.IngressReconciler.InjectRecorder(recorder) + s.Require().NoError(err) + + controller := gomock.NewController(s.T()) + serviceEffectivePolicyReconciler := podreconcilersmocks.NewMockGroupReconciler(controller) + s.podWatcher = pod_reconcilers.NewPodWatcher(s.Mgr.GetClient(), recorder, []string{}, true, true, goset.NewSet[string](), &mocks.MockIntentsReconcilerForTestEnv{}, serviceEffectivePolicyReconciler) + err = s.podWatcher.InitIntentsClientIndices(s.Mgr) + s.Require().NoError(err) + + err = (&controllers.IntentsReconciler{}).InitEndpointsPodNamesIndex(s.Mgr) + s.Require().NoError(err) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForIngress() { + serviceName := "test-server-ingress-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Type: otterizev1alpha3.IntentTypeHTTP, Name: serviceName, + }, + }) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + s.AddDeploymentWithService(serviceName, []string{"1.1.1.1"}, map[string]string{"app": "test"}, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the ingress network policy doesn't exist yet + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + s.AddIngress(serviceName) + + res, err = s.IngressReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: serviceName + "-ingress", + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + s.Require().Len(np.Spec.Ingress, 1) + s.Require().Len(np.Spec.Ingress[0].From, 1) + s.Require().Equal([]v1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "intents.otterize.com/owner-kind": ingressControllerKind, + "intents.otterize.com/service": ingressControllerHashedName, + }}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": ingressControllerNamespace, + }, + }, + }}, np.Spec.Ingress[0].From) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForIngressWithIntentToSVC() { + serviceName := "test-server-ingress-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Type: otterizev1alpha3.IntentTypeHTTP, Name: serviceName, Kind: serviceidentity.KindService, + }, + }) + s.Require().NoError(err) + s.AddDeploymentWithService(serviceName, []string{"1.1.1.1"}, map[string]string{"app": "test"}, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, fmt.Sprintf("%s-service", serviceName)) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + // make sure the ingress network policy doesn't exist yet + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + s.AddIngress(serviceName) + + res, err = s.IngressReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: serviceName + "-ingress", + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + s.Require().Len(np.Spec.Ingress, 1) + s.Require().Len(np.Spec.Ingress[0].From, 1) + s.Require().Equal([]v1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "intents.otterize.com/owner-kind": ingressControllerKind, + "intents.otterize.com/service": ingressControllerHashedName, + }}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": ingressControllerNamespace, + }, + }, + }}, np.Spec.Ingress[0].From) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForIngressWithIntentToDeployment() { + serviceName := "test-server-ingress-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Type: otterizev1alpha3.IntentTypeHTTP, Name: serviceName, Kind: "Deployment", + }, + }) + s.Require().NoError(err) + s.AddDeploymentWithService(serviceName, []string{"1.1.1.1"}, map[string]string{"app": "test"}, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, fmt.Sprintf("%s-deployment", serviceName)) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + // make sure the ingress network policy doesn't exist yet + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + s.AddIngress(serviceName) + + res, err = s.IngressReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: serviceName + "-ingress", + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + + }) + + s.Require().Len(np.Spec.Ingress, 1) + s.Require().Len(np.Spec.Ingress[0].From, 1) + s.Require().Equal([]v1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "intents.otterize.com/owner-kind": ingressControllerKind, + "intents.otterize.com/service": ingressControllerHashedName, + }}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": ingressControllerNamespace, + }, + }, + }}, np.Spec.Ingress[0].From) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestIngressProtectedService_ShadowMode() { + serviceName := "test-server-ingress-test" + + s.AddDeploymentWithService(serviceName, []string{"1.1.1.1"}, map[string]string{"app": "test"}, nil) + + protectedServiceResourceName := "test-protected-service" + protectedService, err := s.AddProtectedService(protectedServiceResourceName, serviceName, s.TestNamespace) + s.Require().NoError(err) + s.Require().NotNil(protectedService) + + protectedService = &otterizev1alpha3.ProtectedService{} + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: protectedServiceResourceName}, protectedService) + assert.NoError(err) + assert.NotEmpty(protectedService) + }) + + res, err := s.defaultDenyReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: protectedServiceResourceName, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + defaultDenyPolicy := &v1.NetworkPolicy{} + defaultDenyPolicyName := fmt.Sprintf("default-deny-%s", serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: defaultDenyPolicyName}, defaultDenyPolicy) + assert.NoError(err) + assert.NotEmpty(defaultDenyPolicy) + }) + + res, err = s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{}) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.True(errors.IsNotFound(err)) + }) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the ingress network policy doesn't exist yet + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + s.AddIngress(serviceName) + + res, err = s.IngressReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: serviceName + "-ingress", + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestIngressWithIntentsProtectedService_ShadowMode() { + serviceName := "test-server-ingress-test" + + s.AddDeploymentWithService(serviceName, []string{"1.1.1.1"}, map[string]string{"app": "test"}, nil) + + protectedServiceResourceName := "test-protected-service" + protectedService, err := s.AddProtectedService(protectedServiceResourceName, serviceName, s.TestNamespace) + s.Require().NoError(err) + s.Require().NotNil(protectedService) + + protectedService = &otterizev1alpha3.ProtectedService{} + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: protectedServiceResourceName}, protectedService) + assert.NoError(err) + assert.NotEmpty(protectedService) + }) + + res, err := s.defaultDenyReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: protectedServiceResourceName, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + defaultDenyPolicy := &v1.NetworkPolicy{} + defaultDenyPolicyName := fmt.Sprintf("default-deny-%s", serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: defaultDenyPolicyName}, defaultDenyPolicy) + assert.NoError(err) + assert.NotEmpty(defaultDenyPolicy) + }) + + _, err = s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Type: otterizev1alpha3.IntentTypeHTTP, Name: serviceName, + }, + }) + s.Require().NoError(err) + + res, err = s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{}) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the ingress network policy doesn't exist yet + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + s.AddIngress(serviceName) + + res, err = s.IngressReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: serviceName + "-ingress", + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + s.Require().Len(np.Spec.Ingress, 1) + s.Require().Len(np.Spec.Ingress[0].From, 1) + s.Require().Equal([]v1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "intents.otterize.com/owner-kind": ingressControllerKind, + "intents.otterize.com/service": ingressControllerHashedName, + }}, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": ingressControllerNamespace, + }, + }, + }}, np.Spec.Ingress[0].From) + +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForLoadBalancer() { + serviceName := "test-server-load-balancer-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Type: otterizev1alpha3.IntentTypeHTTP, Name: serviceName, + }, + }) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + podIps := []string{"1.1.2.1"} + podLabels := map[string]string{"app": "test-load-balancer"} + s.AddDeploymentWithService(serviceName, podIps, podLabels, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the load balancer network policy doesn't exist yet + loadBalancerServiceName := serviceName + "-lb" + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, loadBalancerServiceName) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + + s.AddLoadBalancerService(loadBalancerServiceName, podIps, podLabels) + res, err = s.endpointReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: loadBalancerServiceName, + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + s.Require().Len(np.Spec.Ingress, 1) + s.Require().Len(np.Spec.Ingress[0].From, 0) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForLoadBalancerCreatedAndDeletedWhenLastIntentDeleted() { + serviceName := "test-server-load-balancer-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Name: serviceName, + }, + }) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + netpol := &v1.NetworkPolicy{} + intentNetworkPolicyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: intentNetworkPolicyName}, netpol) + assert.NoError(err) + assert.NotEmpty(netpol) + }) + + podIps := []string{"1.1.2.1"} + podLabels := map[string]string{"app": "test-load-balancer"} + s.AddDeploymentWithService(serviceName, podIps, podLabels, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the load balancer network policy doesn't exist yet + loadBalancerServiceName := serviceName + "-lb" + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, loadBalancerServiceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, netpol) + assert.True(errors.IsNotFound(err)) + }) + + s.AddLoadBalancerService(loadBalancerServiceName, podIps, podLabels) + res, err = s.endpointReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: loadBalancerServiceName, + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, netpol) + assert.NoError(err) + assert.NotEmpty(netpol) + }) + + // Delete the intent and reconcile it + s.Require().NoError(s.Mgr.GetClient().Delete(context.Background(), intents)) + s.WaitUntilCondition(func(assert *assert.Assertions) { + intentsDeleted := &otterizev1alpha3.ClientIntents{} + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: intents.Name}, intentsDeleted) + assert.NoError(err) + assert.NotNil(intentsDeleted.DeletionTimestamp) + }) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + res, err = s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: intentNetworkPolicyName}, &v1.NetworkPolicy{}) + assert.True(errors.IsNotFound(err)) + }) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + res, err = s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, &v1.NetworkPolicy{}) + assert.True(errors.IsNotFound(err)) + }) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForLoadBalancerCreatedAndDoesNotGetDeletedEvenWhenIntentRemovedAsLongAsOneRemains() { + serviceName := "test-server-load-balancer-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Name: serviceName, + }, + }) + s.Require().NoError(err) + + secondaryNamespace := "ns-" + uuid.New().String() + "e" + s.CreateNamespace(secondaryNamespace) + secondIntents, err := s.AddIntentsInNamespace("test-intents-other-ns", "test-client-other-ns", "", secondaryNamespace, []otterizev1alpha3.Intent{{ + Name: fmt.Sprintf("%s.%s", serviceName, s.TestNamespace), + }}) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + res2, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: secondaryNamespace, + Name: secondIntents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res2) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + intentNetworkPolicyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: intentNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + assert.Len(np.Spec.Ingress, 2) + }) + + podIps := []string{"1.1.2.1"} + podLabels := map[string]string{"app": "test-load-balancer"} + s.AddDeploymentWithService(serviceName, podIps, podLabels, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the load balancer network policy doesn't exist yet + loadBalancerServiceName := serviceName + "-lb" + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, loadBalancerServiceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + + s.AddLoadBalancerService(loadBalancerServiceName, podIps, podLabels) + res, err = s.endpointReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: loadBalancerServiceName, + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + // Delete the intent and reconcile it + s.Require().NoError(s.Mgr.GetClient().Delete(context.Background(), intents)) + s.WaitUntilCondition(func(assert *assert.Assertions) { + intentsDeleted := &otterizev1alpha3.ClientIntents{} + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: intents.Name}, intentsDeleted) + assert.NoError(err) + assert.NotNil(intentsDeleted.DeletionTimestamp) + }) + + res, err = s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + netpol := &v1.NetworkPolicy{} + _ = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: intentNetworkPolicyName}, netpol) + assert.NotNil(netpol) + assert.Len(netpol.Spec.Ingress, 1) + }) + + // Check that external policy was not deleted. + externalNetpol := &v1.NetworkPolicy{} + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, externalNetpol) + assert.NoError(err) + assert.Nil(externalNetpol.DeletionTimestamp) + }) + + s.Require().Len(externalNetpol.Spec.Ingress, 1) + s.Require().Len(externalNetpol.Spec.Ingress[0].From, 0) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestNetworkPolicyCreateForNodePort() { + serviceName := "test-server-node-port-test" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Name: serviceName, + }, + }) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + podIps := []string{"1.1.2.1"} + podLabels := map[string]string{"app": "test-load-balancer"} + s.AddDeploymentWithService(serviceName, podIps, podLabels, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the load balancer network policy doesn't exist yet + nodePortServiceName := serviceName + "-np" + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, nodePortServiceName) + + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + + s.AddNodePortService(nodePortServiceName, podIps, podLabels) + res, err = s.endpointReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: nodePortServiceName, + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + s.Require().Len(np.Spec.Ingress, 1) + s.Require().Len(np.Spec.Ingress[0].From, 0) +} + +func (s *ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite) TestEndpointsReconcilerNetworkPoliciesDisabled() { + serviceName := "test-endpoints-reconciler-enforcement-disabled" + intents, err := s.AddIntents("test-intents", "test-client", "Deployment", []otterizev1alpha3.Intent{{ + Name: serviceName, + }, + }) + s.Require().NoError(err) + + res, err := s.EffectivePolicyIntentsReconciler.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: intents.Name, + }, + }) + s.Require().NoError(err) + s.Require().Empty(res) + + // make sure the network policy was created between the two services based on the intents + np := &v1.NetworkPolicy{} + policyName := fmt.Sprintf(otterizev1alpha3.OtterizeSingleNetworkPolicyNameTemplate, serviceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: policyName}, np) + assert.NoError(err) + assert.NotEmpty(np) + }) + + podIps := []string{"1.1.2.1"} + podLabels := map[string]string{"app": "test-load-balancer"} + s.AddDeploymentWithService(serviceName, podIps, podLabels, nil) + + // the ingress reconciler expect the pod watcher labels in order to work + _, err = s.podWatcher.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: s.TestNamespace, Name: serviceName + "-0"}}) + s.Require().NoError(err) + + // make sure the load balancer network policy doesn't exist yet + nodePortServiceName := serviceName + "-np" + externalNetworkPolicyName := fmt.Sprintf(external_traffic.OtterizeExternalNetworkPolicyNameTemplate, nodePortServiceName) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + + s.AddNodePortService(nodePortServiceName, podIps, podLabels) + + netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Off, []serviceidentity.ServiceIdentity{ + { + Namespace: s.TestNamespace, + Name: ingressControllerName, + Kind: "Deployment", + }, + }) + endpointReconcilerWithEnforcementDisabled := external_traffic.NewEndpointsReconciler(s.Mgr.GetClient(), netpolHandler) + recorder := record.NewFakeRecorder(10) + endpointReconcilerWithEnforcementDisabled.InjectRecorder(recorder) + + res, err = endpointReconcilerWithEnforcementDisabled.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: s.TestNamespace, + Name: nodePortServiceName, + }, + }) + + s.Require().NoError(err) + s.Require().Empty(res) + s.WaitUntilCondition(func(assert *assert.Assertions) { + err = s.Mgr.GetClient().Get(context.Background(), types.NamespacedName{Namespace: s.TestNamespace, Name: externalNetworkPolicyName}, np) + assert.True(errors.IsNotFound(err)) + }) + select { + case event := <-recorder.Events: + s.Require().Contains(event, external_traffic.ReasonEnforcementGloballyDisabled) + default: + s.Fail("event not raised") + } +} + +func TestExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite(t *testing.T) { + suite.Run(t, new(ExternalNetworkPolicyReconcilerWithIngressControllersConfiguredTestSuite)) +} diff --git a/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_no_intents_test.go b/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_no_intents_test.go index 2bd2a4232..cda2a6477 100644 --- a/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_no_intents_test.go +++ b/src/operator/controllers/intents_reconcilers/external_traffic_network_policy/external_traffic_network_policy_with_no_intents_test.go @@ -16,6 +16,7 @@ import ( "github.com/otterize/intents-operator/src/operator/effectivepolicy" "github.com/otterize/intents-operator/src/shared/operatorconfig/allowexternaltraffic" "github.com/otterize/intents-operator/src/shared/serviceidresolver" + "github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity" "github.com/otterize/intents-operator/src/shared/testbase" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -67,7 +68,7 @@ func (s *ExternalNetworkPolicyReconcilerWithNoIntentsTestSuite) SetupTest() { s.ControllerManagerTestSuiteBase.SetupTest() recorder := s.Mgr.GetEventRecorderFor("intents-operator") - netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Always) + netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Always, make([]serviceidentity.ServiceIdentity, 0)) netpolReconciler := networkpolicy.NewReconciler(s.Mgr.GetClient(), s.TestEnv.Scheme, netpolHandler, []string{}, goset.NewSet[string](), true, true, []networkpolicy.IngressRuleBuilder{builders.NewIngressNetpolBuilder()}, nil) serviceIdResolver := serviceidresolver.NewResolver(s.Mgr.GetClient()) groupReconciler := effectivepolicy.NewGroupReconciler(s.Mgr.GetClient(), s.TestEnv.Scheme, serviceIdResolver, netpolReconciler) @@ -228,7 +229,7 @@ func (s *ExternalNetworkPolicyReconcilerWithNoIntentsTestSuite) TestEndpointsRec s.AddNodePortService(nodePortServiceName, podIps, podLabels) - netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Off) + netpolHandler := external_traffic.NewNetworkPolicyHandler(s.Mgr.GetClient(), s.TestEnv.Scheme, allowexternaltraffic.Off, make([]serviceidentity.ServiceIdentity, 0)) endpointReconcilerWithEnforcementDisabled := external_traffic.NewEndpointsReconciler(s.Mgr.GetClient(), netpolHandler) recorder := record.NewFakeRecorder(10) endpointReconcilerWithEnforcementDisabled.InjectRecorder(recorder) diff --git a/src/operator/main.go b/src/operator/main.go index b7048e2e4..830f57418 100644 --- a/src/operator/main.go +++ b/src/operator/main.go @@ -51,6 +51,7 @@ import ( "github.com/otterize/intents-operator/src/shared/otterizecloud/otterizecloudclient" "github.com/otterize/intents-operator/src/shared/reconcilergroup" "github.com/otterize/intents-operator/src/shared/serviceidresolver" + "github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity" "github.com/otterize/intents-operator/src/shared/telemetries/componentinfo" "github.com/otterize/intents-operator/src/shared/telemetries/errorreporter" "github.com/otterize/intents-operator/src/shared/telemetries/telemetriesgql" @@ -123,10 +124,7 @@ func main() { signalHandlerCtx := ctrl.SetupSignalHandler() - clusterUID, err := clusterutils.GetOrCreateClusterUID(signalHandlerCtx) - if err != nil { - logrus.WithError(err).Panic("Failed obtaining cluster ID") - } + clusterUID := clusterutils.GetOrCreateClusterUID(signalHandlerCtx) componentinfo.SetGlobalContextId(telemetrysender.Anonymize(clusterUID)) errorreporter.Init(telemetriesgql.TelemetryComponentTypeIntentsOperator, version.Version()) @@ -207,7 +205,7 @@ func main() { kafkaServersStore := kafkaacls.NewServersStore(tlsSource, enforcementConfig.EnableKafkaACL, kafkaacls.NewKafkaIntentsAdmin, enforcementConfig.EnforcementDefaultState) - extNetpolHandler := external_traffic.NewNetworkPolicyHandler(mgr.GetClient(), mgr.GetScheme(), allowExternalTraffic) + extNetpolHandler := external_traffic.NewNetworkPolicyHandler(mgr.GetClient(), mgr.GetScheme(), allowExternalTraffic, operatorconfig.GetIngressControllerServiceIdentities()) endpointReconciler := external_traffic.NewEndpointsReconciler(mgr.GetClient(), extNetpolHandler) ingressRulesBuilder := builders.NewIngressNetpolBuilder() @@ -322,16 +320,11 @@ func main() { logrus.WithError(err).Error("Failed to initialize Otterize Cloud client") } if connectedToCloud { - uploadConfiguration(signalHandlerCtx, otterizeCloudClient, enforcementConfig) + uploadConfiguration(signalHandlerCtx, otterizeCloudClient, enforcementConfig, operatorconfig.GetIngressControllerServiceIdentities()) operator_cloud_client.StartPeriodicallyReportConnectionToCloud(otterizeCloudClient, signalHandlerCtx) - netpolUploader := external_traffic.NewNetworkPolicyUploaderReconciler(mgr.GetClient(), mgr.GetScheme(), otterizeCloudClient) serviceUploadReconciler := external_traffic.NewServiceUploadReconciler(mgr.GetClient(), otterizeCloudClient) ingressUploadReconciler := external_traffic.NewIngressUploadReconciler(mgr.GetClient(), otterizeCloudClient) - if err = netpolUploader.SetupWithManager(mgr); err != nil { - logrus.WithError(err).Panic("unable to initialize NetworkPolicy reconciler") - } - if err = serviceUploadReconciler.SetupWithManager(mgr); err != nil { logrus.WithError(err).Panic("unable to create controller", "controller", "Endpoints") } @@ -515,11 +508,11 @@ func main() { } } -func uploadConfiguration(ctx context.Context, otterizeCloudClient operator_cloud_client.CloudClient, config controllers.EnforcementConfig) { +func uploadConfiguration(ctx context.Context, otterizeCloudClient operator_cloud_client.CloudClient, config controllers.EnforcementConfig, ingressConfigIdentities []serviceidentity.ServiceIdentity) { timeoutCtx, cancel := context.WithTimeout(ctx, viper.GetDuration(otterizecloudclient.CloudClientTimeoutKey)) defer cancel() - err := otterizeCloudClient.ReportIntentsOperatorConfiguration(timeoutCtx, graphqlclient.IntentsOperatorConfigurationInput{ + configInput := graphqlclient.IntentsOperatorConfigurationInput{ GlobalEnforcementEnabled: config.EnforcementDefaultState, NetworkPolicyEnforcementEnabled: config.EnableNetworkPolicy, EgressNetworkPolicyEnforcementEnabled: config.EnableEgressNetworkPolicyReconcilers, @@ -531,7 +524,21 @@ func uploadConfiguration(ctx context.Context, otterizeCloudClient operator_cloud IstioPolicyEnforcementEnabled: config.EnableIstioPolicy, ProtectedServicesEnabled: config.EnableNetworkPolicy, // in this version, protected services are enabled if network policy creation is enabled, regardless of enforcement default state EnforcedNamespaces: config.EnforcedNamespaces.Items(), - }) + } + + if len(ingressConfigIdentities) != 0 { + ingressControllerConfigInput := make([]graphqlclient.IngressControllerConfigInput, 0) + for _, identity := range ingressConfigIdentities { + ingressControllerConfigInput = append(ingressControllerConfigInput, graphqlclient.IngressControllerConfigInput{ + Name: identity.Name, + Namespace: identity.Namespace, + Kind: identity.Kind, + }) + } + configInput.IngressControllerConfig = ingressControllerConfigInput + } + + err := otterizeCloudClient.ReportIntentsOperatorConfiguration(timeoutCtx, configInput) if err != nil { logrus.WithError(err).Error("Failed to report configuration to the cloud") } diff --git a/src/shared/clusterutils/clusterid.go b/src/shared/clusterutils/clusterid.go index 13ce78a4d..6e9fc188f 100644 --- a/src/shared/clusterutils/clusterid.go +++ b/src/shared/clusterutils/clusterid.go @@ -73,7 +73,7 @@ func SetClusterUID(ctx context.Context) (string, error) { return clusterUID, nil } -func GetOrCreateClusterUID(ctx context.Context) (string, error) { +func getOrCreateClusterUID(ctx context.Context) (string, error) { clusterUID, err := GetClusterUID(ctx) if err != nil { if k8serrors.IsNotFound(err) { @@ -87,3 +87,12 @@ func GetOrCreateClusterUID(ctx context.Context) (string, error) { } return clusterUID, nil } + +func GetOrCreateClusterUID(ctx context.Context) string { + clusterUID, err := getOrCreateClusterUID(ctx) + if err != nil { + logrus.WithError(err).Error("failed to create cluster UID, falling back to randomized UID") + return uuid.NewString() + } + return clusterUID +} diff --git a/src/shared/operatorconfig/config.go b/src/shared/operatorconfig/config.go index eadbe8711..946b97aef 100644 --- a/src/shared/operatorconfig/config.go +++ b/src/shared/operatorconfig/config.go @@ -3,6 +3,7 @@ package operatorconfig import ( "github.com/otterize/intents-operator/src/shared/errors" "github.com/otterize/intents-operator/src/shared/operatorconfig/allowexternaltraffic" + "github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity" "github.com/otterize/intents-operator/src/shared/telemetries/telemetriesconfig" "github.com/sirupsen/logrus" "github.com/spf13/pflag" @@ -78,6 +79,7 @@ const ( TelemetryErrorsAPIKeyKey = "telemetry-errors-api-key" TelemetryErrorsAPIKeyDefault = "60a78208a2b4fe714ef9fb3d3fdc0714" AWSAccountsKey = "aws" + IngressControllerConfigKey = "ingressControllers" ) func init() { @@ -137,6 +139,30 @@ func GetRolesAnywhereAWSAccounts() []AWSAccount { return accts } +type IngressControllerConfig struct { + Name string + Namespace string + Kind string +} + +func GetIngressControllerServiceIdentities() []serviceidentity.ServiceIdentity { + controllers := make([]IngressControllerConfig, 0) + err := viper.UnmarshalKey(IngressControllerConfigKey, &controllers) + if err != nil { + logrus.WithError(err).Panic("Failed to unmarshal ingress controller config") + } + + identities := make([]serviceidentity.ServiceIdentity, 0) + for _, controller := range controllers { + identities = append(identities, serviceidentity.ServiceIdentity{ + Name: controller.Name, + Namespace: controller.Namespace, + Kind: controller.Kind, + }) + } + return identities +} + func InitCLIFlags() { // Backwards compatibility, new flags should be added to as ENV variables using viper pflag.String(KafkaServerTLSCertKey, "", "name of tls certificate file") diff --git a/src/shared/otterizecloud/graphqlclient/generated.go b/src/shared/otterizecloud/graphqlclient/generated.go index 154487a52..ccf413139 100644 --- a/src/shared/otterizecloud/graphqlclient/generated.go +++ b/src/shared/otterizecloud/graphqlclient/generated.go @@ -119,6 +119,21 @@ const ( HTTPMethodAll HTTPMethod = "ALL" ) +type IngressControllerConfigInput struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Kind string `json:"kind"` +} + +// GetName returns IngressControllerConfigInput.Name, and is useful for accessing the field via an interface. +func (v *IngressControllerConfigInput) GetName() string { return v.Name } + +// GetNamespace returns IngressControllerConfigInput.Namespace, and is useful for accessing the field via an interface. +func (v *IngressControllerConfigInput) GetNamespace() string { return v.Namespace } + +// GetKind returns IngressControllerConfigInput.Kind, and is useful for accessing the field via an interface. +func (v *IngressControllerConfigInput) GetKind() string { return v.Kind } + type IntentInput struct { Namespace *string `json:"namespace"` ClientName *string `json:"clientName"` @@ -213,17 +228,18 @@ const ( ) type IntentsOperatorConfigurationInput struct { - GlobalEnforcementEnabled bool `json:"globalEnforcementEnabled"` - NetworkPolicyEnforcementEnabled bool `json:"networkPolicyEnforcementEnabled"` - KafkaACLEnforcementEnabled bool `json:"kafkaACLEnforcementEnabled"` - IstioPolicyEnforcementEnabled bool `json:"istioPolicyEnforcementEnabled"` - ProtectedServicesEnabled bool `json:"protectedServicesEnabled"` - EgressNetworkPolicyEnforcementEnabled bool `json:"egressNetworkPolicyEnforcementEnabled"` - AwsIAMPolicyEnforcementEnabled bool `json:"awsIAMPolicyEnforcementEnabled"` - GcpIAMPolicyEnforcementEnabled bool `json:"gcpIAMPolicyEnforcementEnabled"` - AzureIAMPolicyEnforcementEnabled bool `json:"azureIAMPolicyEnforcementEnabled"` - DatabaseEnforcementEnabled bool `json:"databaseEnforcementEnabled"` - EnforcedNamespaces []string `json:"enforcedNamespaces"` + GlobalEnforcementEnabled bool `json:"globalEnforcementEnabled"` + NetworkPolicyEnforcementEnabled bool `json:"networkPolicyEnforcementEnabled"` + KafkaACLEnforcementEnabled bool `json:"kafkaACLEnforcementEnabled"` + IstioPolicyEnforcementEnabled bool `json:"istioPolicyEnforcementEnabled"` + ProtectedServicesEnabled bool `json:"protectedServicesEnabled"` + EgressNetworkPolicyEnforcementEnabled bool `json:"egressNetworkPolicyEnforcementEnabled"` + AwsIAMPolicyEnforcementEnabled bool `json:"awsIAMPolicyEnforcementEnabled"` + GcpIAMPolicyEnforcementEnabled bool `json:"gcpIAMPolicyEnforcementEnabled"` + AzureIAMPolicyEnforcementEnabled bool `json:"azureIAMPolicyEnforcementEnabled"` + DatabaseEnforcementEnabled bool `json:"databaseEnforcementEnabled"` + EnforcedNamespaces []string `json:"enforcedNamespaces"` + IngressControllerConfig []IngressControllerConfigInput `json:"ingressControllerConfig"` } // GetGlobalEnforcementEnabled returns IntentsOperatorConfigurationInput.GlobalEnforcementEnabled, and is useful for accessing the field via an interface. @@ -281,6 +297,11 @@ func (v *IntentsOperatorConfigurationInput) GetEnforcedNamespaces() []string { return v.EnforcedNamespaces } +// GetIngressControllerConfig returns IntentsOperatorConfigurationInput.IngressControllerConfig, and is useful for accessing the field via an interface. +func (v *IntentsOperatorConfigurationInput) GetIngressControllerConfig() []IngressControllerConfigInput { + return v.IngressControllerConfig +} + type InternetConfigInput struct { Domains []*string `json:"domains"` DiscoveredTarget *DNSIPPairInput `json:"discoveredTarget"` diff --git a/src/shared/otterizecloud/graphqlclient/schema.graphql b/src/shared/otterizecloud/graphqlclient/schema.graphql index 95ff18cbb..2580f67ce 100644 --- a/src/shared/otterizecloud/graphqlclient/schema.graphql +++ b/src/shared/otterizecloud/graphqlclient/schema.graphql @@ -720,6 +720,12 @@ input IncomingTrafficIntentInput { source: IncomingInternetSourceInput! } +input IngressControllerConfigInput { + name: String! + namespace: String! + kind: String! +} + input InputAccessGraphFilter { clusterIds: InputIDFilterValue serviceIds: InputIDFilterValue @@ -741,10 +747,13 @@ input InputIDFilterValue { input InputIntegrationAccessGraphFilter { environmentIds: [ID!] + environmentFilterType: IDFilterOperators clusterIds: [ID!] + clusterFilterType: IDFilterOperators + namespaceIds: [ID!] + namespaceFilterType: IDFilterOperators serviceIds: [ID!] serviceFilterType: IDFilterOperators - namespaceIds: [ID!] } input InputTimeFilterValue { @@ -782,8 +791,11 @@ type Integration { type IntegrationAccessGraphFilter { environmentIds: [ID!] + environmentFilterType: IDFilterOperators clusterIds: [ID!] + clusterFilterType: IDFilterOperators namespaceIds: [ID!] + namespaceFilterType: IDFilterOperators serviceIds: [ID!] serviceFilterType: IDFilterOperators lastSeenAfter: Time @@ -923,6 +935,7 @@ input IntentsOperatorConfigurationInput { azureIAMPolicyEnforcementEnabled: Boolean databaseEnforcementEnabled: Boolean enforcedNamespaces: [String!] + ingressControllerConfig: [IngressControllerConfigInput!] } type InternetConfig { diff --git a/src/shared/serviceidresolver/serviceidentity/serviceidentity.go b/src/shared/serviceidresolver/serviceidentity/serviceidentity.go index 44dd73a30..2980da246 100644 --- a/src/shared/serviceidresolver/serviceidentity/serviceidentity.go +++ b/src/shared/serviceidresolver/serviceidentity/serviceidentity.go @@ -48,6 +48,10 @@ func (si *ServiceIdentity) GetNameWithKind() string { return lo.Ternary(si.Kind == "" || si.Kind == KindOtterizeLegacy, si.Name, fmt.Sprintf("%s-%s", si.Name, strings.ToLower(si.Kind))) } +func (si *ServiceIdentity) Equals(other ServiceIdentity) bool { + return si.Name == other.Name && si.Namespace == other.Namespace && si.Kind == other.Kind +} + func (si *ServiceIdentity) String() string { return fmt.Sprintf("%s/%s/%s", si.Kind, si.Namespace, si.Name) }