From e3285103bec289ff7d3267e5977175cc17b3e03c Mon Sep 17 00:00:00 2001 From: John Doe Date: Thu, 18 Jul 2024 16:54:44 +0100 Subject: [PATCH] cherry pick 2.6.0 --- .../vpn.wireguard-operator.io_wireguards.yaml | 4 +- pkg/api/v1alpha1/wireguard_types.go | 2 +- pkg/controllers/wireguard_controller.go | 11 + pkg/controllers/wireguard_controller_test.go | 211 +++++++++++++++--- 4 files changed, 198 insertions(+), 30 deletions(-) diff --git a/config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml b/config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml index 291d23ab..252a2e19 100644 --- a/config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml +++ b/config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml @@ -196,8 +196,8 @@ spec: type: object serviceType: description: A field that specifies the type of Kubernetes service - that should be used for the Wireguard VPN. This could be NodePort - or LoadBalancer, depending on the needs of the deployment. + that should be used for the Wireguard VPN. This could be ClusterIP, + NodePort or LoadBalancer, depending on the needs of the deployment. type: string useWgUserspaceImplementation: description: A boolean field that specifies whether to use the userspace diff --git a/pkg/api/v1alpha1/wireguard_types.go b/pkg/api/v1alpha1/wireguard_types.go index b8b77a96..a2e06150 100644 --- a/pkg/api/v1alpha1/wireguard_types.go +++ b/pkg/api/v1alpha1/wireguard_types.go @@ -43,7 +43,7 @@ type WireguardSpec struct { Address string `json:"address,omitempty"` // A string field that specifies the DNS server(s) to be used by the peers. Dns string `json:"dns,omitempty"` - // A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be NodePort or LoadBalancer, depending on the needs of the deployment. + // A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be ClusterIP, NodePort or LoadBalancer, depending on the needs of the deployment. ServiceType corev1.ServiceType `json:"serviceType,omitempty"` // A field that specifies the value to use for a nodePort ServiceType NodePort int32 `json:"port,omitempty"` diff --git a/pkg/controllers/wireguard_controller.go b/pkg/controllers/wireguard_controller.go index 4ba94609..2476154c 100644 --- a/pkg/controllers/wireguard_controller.go +++ b/pkg/controllers/wireguard_controller.go @@ -431,6 +431,17 @@ func (r *WireguardReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } + if serviceType == corev1.ServiceTypeClusterIP { + if len(svcFound.Spec.Ports) == 0 { + err = r.updateStatus(ctx, req, wireguard, v1alpha1.WgStatusReport{Status: v1alpha1.Pending, Message: "Waiting for service with type ClusterIP to be ready"}) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil + } + } + if wireguard.Status.Address != address || port != wireguard.Status.Port || dnsAddress != wireguard.Status.Dns { updateWireguard := wireguard.DeepCopy() updateWireguard.Status.Address = address diff --git a/pkg/controllers/wireguard_controller_test.go b/pkg/controllers/wireguard_controller_test.go index 56874ec7..b0cac9fe 100644 --- a/pkg/controllers/wireguard_controller_test.go +++ b/pkg/controllers/wireguard_controller_test.go @@ -8,14 +8,14 @@ import ( "time" "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" - "sigs.k8s.io/controller-runtime/pkg/client" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" ) // test helpers @@ -43,18 +43,20 @@ func createNode(address string) error { func reconcileServiceWithTypeNodePort(svcKey client.ObjectKey, nodePort string, port int32) error { // update NodePort service port svc := &corev1.Service{} - k8sClient.Get(context.Background(), svcKey, svc) + Expect(k8sClient.Get(context.Background(), svcKey, svc)).Should(Succeed()) if svc.Spec.Type != corev1.ServiceTypeNodePort { return fmt.Errorf("ReconcileServiceWithTypeNodePort only reconsiles NodePort services") } - nodePortInteger, _ := strconv.ParseInt(nodePort, 10, 32) + nodePortInteger, err := strconv.ParseInt(nodePort, 10, 32) + Expect(err).ToNot(HaveOccurred()) + svc.Spec.Ports = []corev1.ServicePort{{NodePort: int32(nodePortInteger), Port: port}} return k8sClient.Update(context.Background(), svc) } func reconcileServiceWithTypeLoadBalancer(svcKey client.ObjectKey, hostname string) error { svc := &corev1.Service{} - k8sClient.Get(context.Background(), svcKey, svc) + Expect(k8sClient.Get(context.Background(), svcKey, svc)).Should(Succeed()) if svc.Spec.Type != corev1.ServiceTypeLoadBalancer { return fmt.Errorf("ReconcileServiceWithTypeLoadBalancer only reconsiles LoadBalancer services") } @@ -63,6 +65,21 @@ func reconcileServiceWithTypeLoadBalancer(svcKey client.ObjectKey, hostname stri return k8sClient.Status().Update(context.Background(), svc) } +func reconcileServiceWithClusterIP(svcKey client.ObjectKey, port int32) error { + svc := &corev1.Service{} + Expect(k8sClient.Get(context.Background(), svcKey, svc)).Should(Succeed()) + + if svc.Spec.Type != corev1.ServiceTypeClusterIP { + return fmt.Errorf("ReconcileServiceWithClusterIP only reconsiles ClusterIP services") + } + + svc.Spec.Ports = []corev1.ServicePort{{ + Port: port, + TargetPort: intstr.FromInt32(port), + }} + return k8sClient.Status().Update(context.Background(), svc) +} + var _ = Describe("wireguard controller", func() { // Define utility constants for object names and testing timeouts/durations and intervals. @@ -84,43 +101,43 @@ var _ = Describe("wireguard controller", func() { // delete all wg resources wgList := &v1alpha1.WireguardList{} - k8sClient.List(context.Background(), wgList, listOpts...) + Expect(k8sClient.List(context.Background(), wgList, listOpts...)).Should(Succeed()) for _, wg := range wgList.Items { - k8sClient.Delete(context.Background(), &wg) + Expect(k8sClient.Delete(context.Background(), &wg)).Should(Succeed()) } // delete all wg-peer resources peerList := &v1alpha1.WireguardPeerList{} - k8sClient.List(context.Background(), peerList, listOpts...) + Expect(k8sClient.List(context.Background(), peerList, listOpts...)).Should(Succeed()) for _, peer := range peerList.Items { - k8sClient.Delete(context.Background(), &peer) + Expect(k8sClient.Delete(context.Background(), &peer)).Should(Succeed()) } // delete all wg-peer services svcList := &corev1.ServiceList{} - k8sClient.List(context.Background(), svcList, listOpts...) + Expect(k8sClient.List(context.Background(), svcList, listOpts...)).Should(Succeed()) for _, svc := range svcList.Items { - k8sClient.Delete(context.Background(), &svc) + Expect(k8sClient.Delete(context.Background(), &svc)).Should(Succeed()) } // delete all nodes nodeList := &corev1.NodeList{} - k8sClient.List(context.Background(), nodeList, listOpts...) + Expect(k8sClient.List(context.Background(), nodeList, listOpts...)).Should(Succeed()) for _, node := range nodeList.Items { - k8sClient.Delete(context.Background(), &node) + Expect(k8sClient.Delete(context.Background(), &node)).Should(Succeed()) } // delete all secrets secretList := &corev1.SecretList{} - k8sClient.List(context.Background(), secretList, listOpts...) + Expect(k8sClient.List(context.Background(), secretList, listOpts...)).Should(Succeed()) for _, secret := range secretList.Items { - k8sClient.Delete(context.Background(), &secret) + Expect(k8sClient.Delete(context.Background(), &secret)).Should(Succeed()) } // delete all configmaps cList := &corev1.ConfigMapList{} - k8sClient.List(context.Background(), cList, listOpts...) + Expect(k8sClient.List(context.Background(), cList, listOpts...)).Should(Succeed()) for _, c := range cList.Items { - k8sClient.Delete(context.Background(), &c) + Expect(k8sClient.Delete(context.Background(), &c)).Should(Succeed()) } // create kube-dns service @@ -180,7 +197,7 @@ var _ = Describe("wireguard controller", func() { // match labels Eventually(func() map[string]string { svc := &corev1.Service{} - k8sClient.Get(context.Background(), serviceKey, svc) + Expect(k8sClient.Get(context.Background(), serviceKey, svc)).Should(Succeed()) return svc.Spec.Selector }, Timeout, Interval).Should(BeEquivalentTo(expectedLabels)) @@ -188,7 +205,7 @@ var _ = Describe("wireguard controller", func() { Eventually(func() string { wgPeer := &v1alpha1.WireguardPeer{} - k8sClient.Get(context.Background(), wgPeerKey, wgPeer) + Expect(k8sClient.Get(context.Background(), wgPeerKey, wgPeer)).Should(Succeed()) for _, line := range strings.Split(wgPeer.Status.Config, "\n") { if strings.Contains(line, "Endpoint") { return line @@ -239,7 +256,7 @@ var _ = Describe("wireguard controller", func() { // match labels Eventually(func() map[string]string { svc := &corev1.Service{} - k8sClient.Get(context.Background(), serviceKey, svc) + Expect(k8sClient.Get(context.Background(), serviceKey, svc)).Should(Succeed()) return svc.Spec.Selector }, Timeout, Interval).Should(BeEquivalentTo(expectedLabels)) @@ -247,7 +264,7 @@ var _ = Describe("wireguard controller", func() { Eventually(func() string { wgPeer := &v1alpha1.WireguardPeer{} - k8sClient.Get(context.Background(), wgPeerKey, wgPeer) + Expect(k8sClient.Get(context.Background(), wgPeerKey, wgPeer)).Should(Succeed()) for _, line := range strings.Split(wgPeer.Status.Config, "\n") { if strings.Contains(line, "DNS") { return line @@ -289,14 +306,14 @@ var _ = Describe("wireguard controller", func() { // match labels Eventually(func() map[string]string { svc := &corev1.Service{} - k8sClient.Get(context.Background(), serviceKey, svc) + Expect(k8sClient.Get(context.Background(), serviceKey, svc)).Should(Succeed()) return svc.Spec.Selector }, Timeout, Interval).Should(BeEquivalentTo(expectedLabels)) // match service type Eventually(func() corev1.ServiceType { svc := &corev1.Service{} - k8sClient.Get(context.Background(), serviceKey, svc) + Expect(k8sClient.Get(context.Background(), serviceKey, svc)).Should(Succeed()) return svc.Spec.Type }, Timeout, Interval).Should(Equal(corev1.ServiceTypeNodePort)) @@ -406,20 +423,20 @@ Endpoint = %s:%s"`, peerKey.Name, peer.Spec.AllowedIPs, peer.Spec.Address, dnsSe // match labels Eventually(func() map[string]string { svc := &corev1.Service{} - k8sClient.Get(context.Background(), serviceKey, svc) + Expect(k8sClient.Get(context.Background(), serviceKey, svc)).Should(Succeed()) return svc.Spec.Selector }, Timeout, Interval).Should(BeEquivalentTo(expectedLabels)) // match service type Eventually(func() corev1.ServiceType { svc := &corev1.Service{} - k8sClient.Get(context.Background(), serviceKey, svc) + Expect(k8sClient.Get(context.Background(), serviceKey, svc)).Should(Succeed()) return svc.Spec.Type }, Timeout, Interval).Should(Equal(corev1.ServiceTypeLoadBalancer)) Eventually(func() v1alpha1.WireguardStatus { wg := &v1alpha1.Wireguard{} - k8sClient.Get(context.Background(), wgKey, wg) + Expect(k8sClient.Get(context.Background(), wgKey, wg)).Should(Succeed()) return wg.Status }, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{ Address: "", @@ -513,6 +530,146 @@ Endpoint = %s:%s"`, peerKey.Name, peer.Spec.AllowedIPs, peer.Spec.Address, dnsSe }) + It("Should create a WG with ServiceType ClusterIP and WG peer successfully", func() { + expectedAddress := "test-address" + + wgKey := types.NamespacedName{ + Name: wgName, + Namespace: wgNamespace, + } + created := &v1alpha1.Wireguard{ + ObjectMeta: metav1.ObjectMeta{ + Name: wgKey.Name, + Namespace: wgKey.Namespace, + }, + Spec: v1alpha1.WireguardSpec{ + ServiceType: corev1.ServiceTypeClusterIP, + Address: expectedAddress, + }, + } + expectedLabels := map[string]string{"app": "wireguard", "instance": wgKey.Name} + + Expect(k8sClient.Create(context.Background(), created)).Should(Succeed()) + + // service created + serviceName := wgKey.Name + "-svc" + serviceKey := types.NamespacedName{ + Namespace: wgKey.Namespace, + Name: serviceName, + } + + // match labels + Eventually(func() map[string]string { + svc := &corev1.Service{} + //nolint:errcheck + k8sClient.Get(context.Background(), serviceKey, svc) + return svc.Spec.Selector + }, Timeout, Interval).Should(BeEquivalentTo(expectedLabels)) + + // match service type + Eventually(func() corev1.ServiceType { + svc := &corev1.Service{} + //nolint:errcheck + k8sClient.Get(context.Background(), serviceKey, svc) + return svc.Spec.Type + }, Timeout, Interval).Should(Equal(corev1.ServiceTypeClusterIP)) + + Eventually(func() v1alpha1.WireguardStatus { + wg := &v1alpha1.Wireguard{} + //nolint:errcheck + k8sClient.Get(context.Background(), wgKey, wg) + return wg.Status + }, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{ + Address: "", + Status: "pending", + Message: "Waiting for service to be ready", + })) + + Expect(reconcileServiceWithClusterIP(serviceKey, 51820)).Should(Succeed()) + + // check that wireguard resource got the right status after the service is ready + wg := &v1alpha1.Wireguard{} + Eventually(func() v1alpha1.WireguardStatus { + Expect(k8sClient.Get(context.Background(), wgKey, wg)).Should(Succeed()) + return wg.Status + }, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{ + Address: expectedAddress, + Port: "51820", + Status: "ready", + Dns: dnsServiceIp, + Message: "VPN is active!", + })) + + Eventually(func() string { + deploymentKey := types.NamespacedName{ + Name: wgName + "-dep", + Namespace: wgNamespace, + } + deployment := &appsv1.Deployment{} + Expect(k8sClient.Get(context.Background(), deploymentKey, deployment)).Should(Succeed()) + Expect(len(deployment.Spec.Template.Spec.Containers)).Should(Equal(2)) + Expect(deployment.Spec.Template.Spec.Containers[0].Image).Should(Equal(deployment.Spec.Template.Spec.Containers[1].Image)) + return deployment.Spec.Template.Spec.Containers[0].Image + }, Timeout, Interval).Should(Equal(wgTestImage)) + + // create peer + peerKey := types.NamespacedName{ + Name: wgKey.Name + "peer", + Namespace: wgKey.Namespace, + } + peer := &v1alpha1.WireguardPeer{ + ObjectMeta: metav1.ObjectMeta{ + Name: peerKey.Name, + Namespace: peerKey.Namespace, + }, + Spec: v1alpha1.WireguardPeerSpec{ + WireguardRef: wgKey.Name, + }, + } + Expect(k8sClient.Create(context.Background(), peer)).Should(Succeed()) + + //get peer secret + wgSecretKeyName := types.NamespacedName{ + Name: wgKey.Name, + Namespace: wgKey.Namespace, + } + wgSecret := &corev1.Secret{} + Eventually(func() error { + return k8sClient.Get(context.Background(), wgSecretKeyName, wgSecret) + }, Timeout, Interval).Should(Succeed()) + wgPublicKey := string(wgSecret.Data["publicKey"]) + + Eventually(func() string { + Expect(k8sClient.Get(context.Background(), peerKey, peer)).Should(Succeed()) + print(peer.Status.Message) + return peer.Spec.Address + }, Timeout, Interval).Should(Equal("10.8.0.2")) + + Eventually(func() v1alpha1.WireguardPeerStatus { + Expect(k8sClient.Get(context.Background(), peerKey, peer)).Should(Succeed()) + return peer.Status + }, Timeout, Interval).Should(Equal(v1alpha1.WireguardPeerStatus{ + Config: fmt.Sprintf(` + echo " + [Interface] + PrivateKey = $(kubectl get secret %s-peer --template={{.data.privateKey}} -n default | base64 -d) + Address = %s + DNS = %s, %s.svc.cluster.local + + [Peer] + PublicKey = %s + AllowedIPs = 0.0.0.0/0 + Endpoint = %s:%s"`, peerKey.Name, peer.Spec.Address, dnsServiceIp, peer.Namespace, wgPublicKey, expectedAddress, wg.Status.Port), + Status: "ready", + Message: "Peer configured", + })) + + Eventually(func() error { + return k8sClient.Get(context.Background(), wgSecretKeyName, wgSecret) + }, Timeout, Interval).Should(Succeed()) + + }) + for _, useWgUserspace := range []bool{true, false} { testTextPrefix := "uses" if !useWgUserspace { @@ -547,7 +704,7 @@ Endpoint = %s:%s"`, peerKey.Name, peer.Spec.AllowedIPs, peer.Spec.Address, dnsSe Eventually(func() []string { dep := &appsv1.Deployment{} - k8sClient.Get(context.Background(), depKey, dep) + Expect(k8sClient.Get(context.Background(), depKey, dep)).Should(Succeed()) fmt.Println(dep) for _, c := range dep.Spec.Template.Spec.Containers { if c.Name == "agent" {