/*
Copyright 2020 The cert-manager Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package approval

import (
	"context"
	"crypto/x509"
	"fmt"
	"strings"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	crdapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/util/retry"

	"github.com/cert-manager/cert-manager/e2e-tests/framework"
	testutil "github.com/cert-manager/cert-manager/e2e-tests/framework/util"
	e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util"
	apiutil "github.com/cert-manager/cert-manager/pkg/api/util"
	cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
	clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned"
	"github.com/cert-manager/cert-manager/test/unit/gen"
)

// This test ensures that the approval condition may only be set by users who
// have the correct RBAC permissions.
var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() {
	f := framework.NewDefaultFramework("approval-certificaterequests")

	var (
		sa       *corev1.ServiceAccount
		saclient clientset.Interface
		request  *cmapi.CertificateRequest

		crd       *crdapi.CustomResourceDefinition
		crdclient crdclientset.Interface
		group     string
	)

	// isNotFoundError returns true if an error from the cert-manager admission
	// webhook contains a is not found error.
	isNotFoundError := func(issuerRef cmmeta.ObjectReference, err error) bool {
		if err == nil {
			return false
		}
		return strings.HasSuffix(
			strings.TrimSpace(err.Error()),
			fmt.Sprintf("Forbidden: referenced signer resource does not exist: %v", issuerRef),
		) || strings.HasSuffix(strings.TrimSpace(err.Error()), "the server could not find the requested resource")
	}

	// isNotFoundError returns true if an error from the cert-manager admission
	// webhook contains a context deadline error.
	isTimeoutError := func(err error) bool {
		if err == nil {
			return false
		}
		return strings.HasSuffix(strings.TrimSpace(err.Error()), "context deadline exceeded")
	}

	// isNotFoundError returns true if an error from the cert-manager admission
	// webhook contains a permissions error when the client attempts to approve
	// or deny a CertificateRequest.
	isPermissionError := func(sa *corev1.ServiceAccount, issuerRef cmmeta.ObjectReference, err error) bool {
		if err == nil {
			return false
		}
		return strings.HasSuffix(
			strings.TrimSpace(err.Error()),
			fmt.Sprintf("Forbidden: user \"system:serviceaccount:%s:%s\" does not have permissions to set approved/denied conditions for issuer %v",
				sa.Namespace, sa.Name, issuerRef),
		)
	}

	// retryNotFound returns true when either the resource is not found for the
	// issuer, or the webhook returns a context deadline error. Useful for
	// retrying a request when the expected response is a different or no error.
	retryOnNotFound := func(issuerRef cmmeta.ObjectReference) func(error) bool {
		return func(err error) bool {
			return isNotFoundError(issuerRef, err) || isTimeoutError(err)
		}
	}

	JustBeforeEach(func() {
		var err error
		crdclient, err = crdclientset.NewForConfig(f.KubeClientConfig)
		Expect(err).NotTo(HaveOccurred())
		group = e2eutil.RandomSubdomain("example.io")

		sa, err = f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Create(context.TODO(), &corev1.ServiceAccount{
			ObjectMeta: metav1.ObjectMeta{
				GenerateName: "test-sa-",
				Namespace:    f.Namespace.Name,
			},
		}, metav1.CreateOptions{})
		Expect(err).NotTo(HaveOccurred())

		role, err := f.KubeClientSet.RbacV1().Roles(f.Namespace.Name).Create(context.TODO(), &rbacv1.Role{
			ObjectMeta: metav1.ObjectMeta{
				GenerateName: "certificaterequest-creator-",
				Namespace:    f.Namespace.Name,
			},
			Rules: []rbacv1.PolicyRule{
				{
					Verbs:     []string{"create"},
					APIGroups: []string{"cert-manager.io"},
					Resources: []string{"certificaterequests"},
				},
				{
					Verbs:     []string{"update"},
					APIGroups: []string{"cert-manager.io"},
					Resources: []string{"certificaterequests/status"},
				},
			},
		}, metav1.CreateOptions{})
		Expect(err).NotTo(HaveOccurred())

		By("Creating certificaterequest-creator rolebinding for ServiceAccount")
		_, err = f.KubeClientSet.RbacV1().RoleBindings(f.Namespace.Name).Create(context.TODO(), &rbacv1.RoleBinding{
			ObjectMeta: metav1.ObjectMeta{
				GenerateName: "certificaterequest-creator-",
				Namespace:    f.Namespace.Name,
			},
			Subjects: []rbacv1.Subject{
				{
					Kind:      "ServiceAccount",
					Name:      sa.Name,
					Namespace: f.Namespace.Name,
				},
			},
			RoleRef: rbacv1.RoleRef{
				APIGroup: "rbac.authorization.k8s.io",
				Kind:     "Role",
				Name:     role.Name,
			},
		}, metav1.CreateOptions{})
		Expect(err).NotTo(HaveOccurred())

		// Manually create a Secret to be populated with Service Account token
		// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-service-account-api-token
		secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				GenerateName: "sa-secret-",
				Name:         f.Namespace.Name,
				Annotations: map[string]string{
					"kubernetes.io/service-account.name": sa.Name,
				},
			},
			Type: corev1.SecretTypeServiceAccountToken,
		}, metav1.CreateOptions{})

		var (
			token []byte
			ok    bool
		)
		err = wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Second*10, true, func(ctx context.Context) (bool, error) {
			secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, secret.Name, metav1.GetOptions{})
			if err != nil {
				return false, err
			}

			if len(secret.Data) == 0 {
				return false, nil
			}
			if token, ok = secret.Data["token"]; !ok {
				return false, nil
			}

			return true, nil
		})
		Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error: %s", err))

		By("Building ServiceAccount kubernetes clientset")

		kubeConfig, err := testutil.LoadConfig(f.Config.KubeConfig, f.Config.KubeContext)
		Expect(err).NotTo(HaveOccurred())

		kubeConfig.QPS = 9000
		kubeConfig.Burst = 9000
		kubeConfig.BearerToken = fmt.Sprintf("%s", token)
		kubeConfig.CertData = nil
		kubeConfig.KeyData = nil
		kubeConfig.Timeout = time.Second * 20

		saclient, err = clientset.NewForConfig(kubeConfig)
		Expect(err).NotTo(HaveOccurred())

		csr, _, err := gen.CSR(x509.RSA)
		Expect(err).NotTo(HaveOccurred())

		request = gen.CertificateRequest("",
			gen.SetCertificateRequestNamespace(f.Namespace.Name),
			gen.SetCertificateRequestCSR(csr),
			gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
				Name:  "test-issuer",
				Kind:  "Issuer",
				Group: group,
			}),
		)
		request.GenerateName = "test-request-"

		request, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), request, metav1.CreateOptions{})
		Expect(err).NotTo(HaveOccurred())
	})

	JustAfterEach(func() {
		err := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Delete(context.TODO(), sa.Name, metav1.DeleteOptions{})
		Expect(err).NotTo(HaveOccurred())
		err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Delete(context.TODO(), request.Name, metav1.DeleteOptions{})
		Expect(err).NotTo(HaveOccurred())

		if crd != nil {
			By("Removing CustomResource Definition")
			err = crdclient.ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), crd.Name, metav1.DeleteOptions{})
			Expect(err).NotTo(HaveOccurred())
		}
		crd = nil
	})

	It("attempting to approve a certificate request without the approve permission should error", func() {
		createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped)
		approvedCR := request.DeepCopy()
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		err := retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error {
			_, err := saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
			return err
		})
		Expect(isPermissionError(sa, approvedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error())
	})

	It("attempting to deny a certificate request without the approve permission should error", func() {
		createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped)
		deniedCR := request.DeepCopy()
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		err := retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error {
			_, err := saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
			return err
		})
		Expect(isPermissionError(sa, deniedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error())
	})

	It("a service account with the approve permissions for a resource that doesn't exist attempting to approve should error", func() {
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group))

		approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")

		Consistently(func() bool {
			err := retry.OnError(retry.DefaultBackoff, isTimeoutError, func() error {
				_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
				return err
			})
			return isNotFoundError(approvedCR.Spec.IssuerRef, err)
		}).Should(BeTrue())
	})

	It("a service account with the approve permissions for a resource that doesn't exist attempting to deny should error", func() {
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group))

		deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Consistently(func() bool {
			err := retry.OnError(retry.DefaultBackoff, isTimeoutError, func() error {
				_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
				return err
			})
			return isNotFoundError(deniedCR.Spec.IssuerRef, err)
		}).Should(BeTrue())
	})

	It("a service account with the approve permissions for cluster scoped issuers.example.io/* should be able to approve requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group))

		approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
			return err
		})).ToNot(HaveOccurred())
	})

	It("a service account with the approve permissions for cluster scoped issuers.example.io/* should be able to deny requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group))

		deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
			return err
		})).ToNot(HaveOccurred())
	})

	It("a service account with the approve permissions for cluster scoped issuers.example.io/test-issuer should be able to approve requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group))

		approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
			return err
		})).ToNot(HaveOccurred())
	})

	It("a service account with the approve permissions for cluster scoped issuers.example.io/<namespace>.test-issuer should not be able to approve requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", f.Namespace.Name, group))

		approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
			return err
		})
		Expect(isPermissionError(sa, approvedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error())
	})

	It("a service account with the approve permissions for namespaced scoped issuers.example.io/<namespace>.test-issuer should be able to approve requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", group, f.Namespace.Name))

		approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
			return err
		})).ToNot(HaveOccurred())
	})

	It("a service account with the approve permissions for namespaced scoped issuers.example.io/test-issuer should not be able to approve requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group))

		approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{})
			return err
		})
		Expect(isPermissionError(sa, approvedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error())
	})

	//

	It("a service account with the approve permissions for cluster scoped issuers.example.io/test-issuer should be able to deny requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group))

		deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
			return err
		})).ToNot(HaveOccurred())
	})

	It("a service account with the approve permissions for cluster scoped issuers.example.io/<namespace>.test-issuer should not be able to deny requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", f.Namespace.Name, group))

		deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
			return err
		})
		Expect(isPermissionError(sa, deniedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error())
	})

	It("a service account with the approve permissions for namespaced scoped issuers.example.io/<namespace>.test-issuer should be able to deny requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", group, f.Namespace.Name))

		deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
			return err
		})).ToNot(HaveOccurred())
	})

	It("a service account with the approve permissions for namespaced scoped issuers.example.io/test-issuer should not be able to denied requests", func() {
		crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped)
		bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group))

		deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{})
		Expect(err).NotTo(HaveOccurred())
		apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e")
		err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error {
			_, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{})
			return err
		})
		Expect(isPermissionError(sa, deniedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error())
	})
})

func createCRD(crdclient crdclientset.Interface, group, plural, kind string, scope crdapi.ResourceScope) *crdapi.CustomResourceDefinition {
	crd, err := crdclient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), &crdapi.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s.%s", plural, group),
		},
		Spec: crdapi.CustomResourceDefinitionSpec{
			Group: group,
			Names: crdapi.CustomResourceDefinitionNames{
				Kind:   kind,
				Plural: plural,
			},
			Versions: []crdapi.CustomResourceDefinitionVersion{
				{
					Name:    "v1alpha1",
					Served:  true,
					Storage: true,
					Schema: &crdapi.CustomResourceValidation{
						OpenAPIV3Schema: &crdapi.JSONSchemaProps{
							Type:       "object",
							Properties: map[string]crdapi.JSONSchemaProps{},
						},
					},
				},
			},
			Scope: scope,
		},
	}, metav1.CreateOptions{})
	Expect(err).ToNot(HaveOccurred())
	return crd
}

func bindServiceAccountToApprove(f *framework.Framework, sa *corev1.ServiceAccount, resourceName string) {
	clusterrole, err := f.KubeClientSet.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "certificaterequest-approver-",
		},
		Rules: []rbacv1.PolicyRule{
			{
				APIGroups:     []string{"cert-manager.io"},
				Resources:     []string{"signers"},
				Verbs:         []string{"approve"},
				ResourceNames: []string{resourceName},
			},
		},
	}, metav1.CreateOptions{})
	Expect(err).NotTo(HaveOccurred())

	_, err = f.KubeClientSet.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "certificaterequest-approver-",
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      sa.Name,
				Namespace: sa.Namespace,
			},
		},
		RoleRef: rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "ClusterRole",
			Name:     clusterrole.Name,
		},
	}, metav1.CreateOptions{})
	Expect(err).NotTo(HaveOccurred())
}
