// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e import ( "strings" "testing" "time" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" ) // See [TestMain] for test requirements. func TestProxyGroupPolicy(t *testing.T) { if tnClient == nil { t.Skip("TestProxyGroupPolicy requires a working tailnet client") } // Apply deny-all policy denyAllPolicy := &tsapi.ProxyGroupPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "deny-all", Namespace: metav1.NamespaceDefault, }, Spec: tsapi.ProxyGroupPolicySpec{ Ingress: []string{}, Egress: []string{}, }, } createAndCleanup(t, kubeClient, denyAllPolicy) <-time.After(time.Second * 2) // Attempt to create an egress Service within the default namespace, the above policy should // reject it. egressService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "egress-to-proxy-group", Namespace: metav1.NamespaceDefault, Annotations: map[string]string{ "tailscale.com/tailnet-fqdn": "test.something.ts.net", "tailscale.com/proxy-group": "test", }, }, Spec: corev1.ServiceSpec{ ExternalName: "placeholder", Type: corev1.ServiceTypeExternalName, Ports: []corev1.ServicePort{ { Port: 8080, Protocol: corev1.ProtocolTCP, Name: "http", }, }, }, } err := createAndCleanupErr(t, kubeClient, egressService) switch { case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): case err != nil: t.Fatalf("expected forbidden error, got: %v", err) default: t.Fatal("expected error when creating egress service") } // Attempt to create an ingress Service within the default namespace, the above policy should // reject it. ingressService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "ingress-to-proxy-group", Namespace: metav1.NamespaceDefault, Annotations: map[string]string{ "tailscale.com/proxy-group": "test", }, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, LoadBalancerClass: new("tailscale"), Ports: []corev1.ServicePort{ { Port: 8080, Protocol: corev1.ProtocolTCP, Name: "http", }, }, }, } err = createAndCleanupErr(t, kubeClient, ingressService) switch { case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): case err != nil: t.Fatalf("expected forbidden error, got: %v", err) default: t.Fatal("expected error when creating ingress service") } // Attempt to create an Ingress within the default namespace, the above policy should reject it ingress := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "ingress-to-proxy-group", Namespace: metav1.NamespaceDefault, Annotations: map[string]string{ "tailscale.com/proxy-group": "test", }, }, Spec: networkingv1.IngressSpec{ IngressClassName: new("tailscale"), DefaultBackend: &networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: "nginx", Port: networkingv1.ServiceBackendPort{ Number: 80, }, }, }, TLS: []networkingv1.IngressTLS{ { Hosts: []string{"nginx"}, }, }, }, } err = createAndCleanupErr(t, kubeClient, ingress) switch { case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): case err != nil: t.Fatalf("expected forbidden error, got: %v", err) default: t.Fatal("expected error when creating ingress") } // Add policy to allow ingress/egress using the "test" proxy-group. This should be merged with the deny-all // policy so they do not conflict. allowTestPolicy := &tsapi.ProxyGroupPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "allow-test", Namespace: metav1.NamespaceDefault, }, Spec: tsapi.ProxyGroupPolicySpec{ Ingress: []string{"test"}, Egress: []string{"test"}, }, } createAndCleanup(t, kubeClient, allowTestPolicy) <-time.After(time.Second * 2) // With this policy in place, the above ingress/egress resources should be allowed to be created. createAndCleanup(t, kubeClient, egressService) createAndCleanup(t, kubeClient, ingressService) createAndCleanup(t, kubeClient, ingress) }