@@ -29,9 +29,11 @@ import (
2929 "time"
3030
3131 corev1 "k8s.io/api/core/v1"
32+ networkingv1 "k8s.io/api/networking/v1"
3233 k8serrors "k8s.io/apimachinery/pkg/api/errors"
3334 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3435 "k8s.io/apimachinery/pkg/runtime/schema"
36+ "k8s.io/apimachinery/pkg/util/intstr"
3537 "k8s.io/client-go/dynamic"
3638 "k8s.io/client-go/kubernetes"
3739 "k8s.io/client-go/util/retry"
@@ -713,6 +715,176 @@ var _ = Describe("External Secrets Operator End-to-End test scenarios", Ordered,
713715 })
714716 })
715717
718+ Context ("Proxy Egress NetworkPolicy" , func () {
719+ const (
720+ proxyEgressNPName = "eso-sys-proxy-egress-core"
721+ testProxyURL = "http://proxy.test.example.com:3128"
722+ testProxyPort = 3128
723+ )
724+
725+ // restoreESCProxyConfig clears proxy and user-defined NetworkPolicy fields from the CR.
726+ restoreESCProxyConfig := func () {
727+ err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
728+ existingCR := & operatorv1alpha1.ExternalSecretsConfig {}
729+ if err := runtimeClient .Get (ctx , client.ObjectKey {Name : "cluster" }, existingCR ); err != nil {
730+ return err
731+ }
732+ updatedCR := existingCR .DeepCopy ()
733+ updatedCR .Spec .ApplicationConfig .Proxy = nil
734+ updatedCR .Spec .ControllerConfig .NetworkPolicies = nil
735+ return runtimeClient .Update (ctx , updatedCR )
736+ })
737+ Expect (err ).NotTo (HaveOccurred (), "should restore ExternalSecretsConfig proxy config to baseline" )
738+ }
739+
740+ It ("should not create eso-sys-proxy-egress-core when no proxy is configured" , func () {
741+ By ("Verifying eso-sys-proxy-egress-core does not exist without proxy configuration" )
742+ Consistently (func (g Gomega ) {
743+ _ , err := clientset .NetworkingV1 ().NetworkPolicies (operandNamespace ).Get (ctx , proxyEgressNPName , metav1.GetOptions {})
744+ g .Expect (err ).To (HaveOccurred (), "%s should not exist when no proxy is configured" , proxyEgressNPName )
745+ }, 15 * time .Second , 3 * time .Second ).Should (Succeed ())
746+ })
747+
748+ It ("should create eso-sys-proxy-egress-core when proxy is Managed and proxy URL is set" , func () {
749+ defer restoreESCProxyConfig ()
750+
751+ By ("Setting proxy with Managed mode in ExternalSecretsConfig" )
752+ err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
753+ existingCR := & operatorv1alpha1.ExternalSecretsConfig {}
754+ if err := runtimeClient .Get (ctx , client.ObjectKey {Name : "cluster" }, existingCR ); err != nil {
755+ return err
756+ }
757+ updatedCR := existingCR .DeepCopy ()
758+ updatedCR .Spec .ApplicationConfig .Proxy = & operatorv1alpha1.ProxyConfig {
759+ HTTPProxy : testProxyURL ,
760+ NetworkPolicyProvisioning : operatorv1alpha1 .ManagementStateManaged ,
761+ }
762+ return runtimeClient .Update (ctx , updatedCR )
763+ })
764+ Expect (err ).NotTo (HaveOccurred (), "should set proxy config in ExternalSecretsConfig" )
765+
766+ By ("Verifying eso-sys-proxy-egress-core NetworkPolicy is created with correct egress port" )
767+ Eventually (func (g Gomega ) {
768+ np , err := clientset .NetworkingV1 ().NetworkPolicies (operandNamespace ).Get (ctx , proxyEgressNPName , metav1.GetOptions {})
769+ g .Expect (err ).NotTo (HaveOccurred (), "%s should be created when proxy Managed" , proxyEgressNPName )
770+ g .Expect (np .Spec .Egress ).NotTo (BeEmpty (), "NP should have egress rules" )
771+
772+ var foundPort bool
773+ for _ , rule := range np .Spec .Egress {
774+ for _ , port := range rule .Ports {
775+ if port .Port != nil && port .Port .IntValue () == testProxyPort {
776+ foundPort = true
777+ }
778+ }
779+ }
780+ g .Expect (foundPort ).To (BeTrue (), "NP should allow egress to proxy port %d" , testProxyPort )
781+ }, 2 * time .Minute , 5 * time .Second ).Should (Succeed ())
782+ })
783+
784+ It ("should delete eso-sys-proxy-egress-core when mode switches to Unmanaged" , func () {
785+ defer restoreESCProxyConfig ()
786+
787+ By ("Setting proxy with Managed mode to create the NP" )
788+ err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
789+ existingCR := & operatorv1alpha1.ExternalSecretsConfig {}
790+ if err := runtimeClient .Get (ctx , client.ObjectKey {Name : "cluster" }, existingCR ); err != nil {
791+ return err
792+ }
793+ updatedCR := existingCR .DeepCopy ()
794+ updatedCR .Spec .ApplicationConfig .Proxy = & operatorv1alpha1.ProxyConfig {
795+ HTTPProxy : testProxyURL ,
796+ NetworkPolicyProvisioning : operatorv1alpha1 .ManagementStateManaged ,
797+ }
798+ return runtimeClient .Update (ctx , updatedCR )
799+ })
800+ Expect (err ).NotTo (HaveOccurred ())
801+
802+ By ("Waiting for eso-sys-proxy-egress-core to be created" )
803+ Eventually (func (g Gomega ) {
804+ _ , err := clientset .NetworkingV1 ().NetworkPolicies (operandNamespace ).Get (ctx , proxyEgressNPName , metav1.GetOptions {})
805+ g .Expect (err ).NotTo (HaveOccurred ())
806+ }, 2 * time .Minute , 5 * time .Second ).Should (Succeed ())
807+
808+ By ("Switching networkPolicyProvisioning to Unmanaged" )
809+ err = retry .RetryOnConflict (retry .DefaultRetry , func () error {
810+ existingCR := & operatorv1alpha1.ExternalSecretsConfig {}
811+ if err := runtimeClient .Get (ctx , client.ObjectKey {Name : "cluster" }, existingCR ); err != nil {
812+ return err
813+ }
814+ updatedCR := existingCR .DeepCopy ()
815+ updatedCR .Spec .ApplicationConfig .Proxy = & operatorv1alpha1.ProxyConfig {
816+ HTTPProxy : testProxyURL ,
817+ NetworkPolicyProvisioning : operatorv1alpha1 .ManagementStateUnmanaged ,
818+ }
819+ return runtimeClient .Update (ctx , updatedCR )
820+ })
821+ Expect (err ).NotTo (HaveOccurred ())
822+
823+ By ("Verifying eso-sys-proxy-egress-core is deleted after switching to Unmanaged" )
824+ Eventually (func (g Gomega ) {
825+ _ , err := clientset .NetworkingV1 ().NetworkPolicies (operandNamespace ).Get (ctx , proxyEgressNPName , metav1.GetOptions {})
826+ g .Expect (err ).To (HaveOccurred (), "%s should be deleted when mode is Unmanaged" , proxyEgressNPName )
827+ }, 2 * time .Minute , 5 * time .Second ).Should (Succeed ())
828+ })
829+
830+ It ("should apply eso-user- prefix to user-defined NetworkPolicy names" , func () {
831+ defer restoreESCProxyConfig ()
832+
833+ const userPolicyName = "my-custom-egress"
834+ const prefixedPolicyName = "eso-user-" + userPolicyName
835+
836+ By ("Adding a user-defined NetworkPolicy to ExternalSecretsConfig spec" )
837+ tcpProto := corev1 .ProtocolTCP
838+ port443 := intstr .FromInt32 (443 )
839+ err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
840+ existingCR := & operatorv1alpha1.ExternalSecretsConfig {}
841+ if err := runtimeClient .Get (ctx , client.ObjectKey {Name : "cluster" }, existingCR ); err != nil {
842+ return err
843+ }
844+ updatedCR := existingCR .DeepCopy ()
845+ updatedCR .Spec .ControllerConfig .NetworkPolicies = []operatorv1alpha1.NetworkPolicy {
846+ {
847+ Name : userPolicyName ,
848+ ComponentName : operatorv1alpha1 .CoreController ,
849+ Egress : []networkingv1.NetworkPolicyEgressRule {
850+ {
851+ Ports : []networkingv1.NetworkPolicyPort {
852+ {Protocol : & tcpProto , Port : & port443 },
853+ },
854+ },
855+ },
856+ },
857+ }
858+ return runtimeClient .Update (ctx , updatedCR )
859+ })
860+ Expect (err ).NotTo (HaveOccurred (), "should add user-defined NetworkPolicy to ExternalSecretsConfig" )
861+
862+ By (fmt .Sprintf ("Verifying %s exists (with eso-user- prefix)" , prefixedPolicyName ))
863+ Eventually (func (g Gomega ) {
864+ np , err := clientset .NetworkingV1 ().NetworkPolicies (operandNamespace ).Get (ctx , prefixedPolicyName , metav1.GetOptions {})
865+ g .Expect (err ).NotTo (HaveOccurred (), "%s should exist" , prefixedPolicyName )
866+ g .Expect (np .Spec .Egress ).NotTo (BeEmpty ())
867+ }, 2 * time .Minute , 5 * time .Second ).Should (Succeed ())
868+
869+ By (fmt .Sprintf ("Verifying unprefixed name '%s' does not exist as a separate object" , userPolicyName ))
870+ _ , err = clientset .NetworkingV1 ().NetworkPolicies (operandNamespace ).Get (ctx , userPolicyName , metav1.GetOptions {})
871+ Expect (err ).To (HaveOccurred (), "unprefixed '%s' should not exist as a K8s object" , userPolicyName )
872+ })
873+
874+ It ("should set skip-np-cleanup annotation on ExternalSecretsConfig after first reconcile" , func () {
875+ By ("Verifying skip-np-cleanup annotation is set on ExternalSecretsConfig" )
876+ Eventually (func (g Gomega ) {
877+ existingCR := & operatorv1alpha1.ExternalSecretsConfig {}
878+ err := runtimeClient .Get (ctx , client.ObjectKey {Name : "cluster" }, existingCR )
879+ g .Expect (err ).NotTo (HaveOccurred ())
880+ g .Expect (existingCR .GetAnnotations ()).To (
881+ HaveKey ("externalsecretsconfig.operator.openshift.io/skip-np-cleanup-check" ),
882+ "skip-np-cleanup annotation should be set after first reconcile" ,
883+ )
884+ }, 2 * time .Minute , 5 * time .Second ).Should (Succeed ())
885+ })
886+ })
887+
716888 AfterAll (func () {
717889 By ("Deleting the externalsecrets.openshift.operator.io/cluster CR" )
718890 loader .DeleteFromFile (testassets .ReadFile , externalSecretsFile , "" )
0 commit comments