github.com/jenkins-x/jx/v2@v2.1.155/pkg/kube/pki/certificate.go (about)

     1  package pki
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/jenkins-x/jx-logging/pkg/log"
    10  	"github.com/jenkins-x/jx/v2/pkg/util"
    11  
    12  	kubeservices "github.com/jenkins-x/jx/v2/pkg/kube/services"
    13  	certutil "github.com/jetstack/cert-manager/pkg/api/util"
    14  	certmng "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
    15  	certclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
    16  	"github.com/pkg/errors"
    17  	v1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/util/wait"
    20  	"k8s.io/apimachinery/pkg/watch"
    21  	"k8s.io/client-go/kubernetes"
    22  )
    23  
    24  // CertSecretPrefix used as prefix for all certificate object names
    25  const CertSecretPrefix = "tls-"
    26  
    27  // Certificate keeps some information related to a certificate issued by cert-manager
    28  type Certificate struct {
    29  	// Name certificate name
    30  	Name string
    31  	//Namespace certificate namespace
    32  	Namespace string
    33  }
    34  
    35  // WaitCertificateIssuedReady wait for a certificate issued by cert-manager until is ready or the timeout is reached
    36  func WaitCertificateIssuedReady(client certclient.Interface, name string, ns string, timeout time.Duration) error {
    37  	err := wait.PollImmediate(time.Second, timeout, func() (bool, error) {
    38  		cert, err := client.CertmanagerV1alpha1().Certificates(ns).Get(name, metav1.GetOptions{})
    39  		if err != nil {
    40  			return false, nil
    41  		}
    42  		isReady := certutil.CertificateHasCondition(cert, certmng.CertificateCondition{
    43  			Type:   certmng.CertificateConditionReady,
    44  			Status: certmng.ConditionTrue,
    45  		})
    46  		if !isReady {
    47  			return false, nil
    48  		}
    49  		log.Logger().Infof("Ready Cert: %s", util.ColorInfo(name))
    50  		return true, nil
    51  	})
    52  	if err != nil {
    53  		return errors.Wrapf(err, "waiting for certificate %q to be ready in namespace %q.", name, ns)
    54  	}
    55  	return nil
    56  }
    57  
    58  // WaitCertificateExists waits until the timeout for the certificate with the provided name to be available in the certificates list
    59  func WaitCertificateExists(client certclient.Interface, name string, ns string, timeout time.Duration) error {
    60  	err := wait.PollImmediate(time.Second, timeout, func() (bool, error) {
    61  		_, err := client.CertmanagerV1alpha1().Certificates(ns).Get(name, metav1.GetOptions{})
    62  		if err != nil {
    63  			return false, nil
    64  		}
    65  		return true, nil
    66  	})
    67  	if err != nil {
    68  		return errors.Wrapf(err, "waiting for certificate %q to be created in namespace %q.", name, ns)
    69  	}
    70  	return nil
    71  }
    72  
    73  // CleanAllCerts removes all certs and their associated secrets which hold a TLS certificated issued by cert-manager
    74  func CleanAllCerts(client kubernetes.Interface, certclient certclient.Interface, ns string) error {
    75  	return cleanCerts(client, certclient, ns, func(cert string) bool {
    76  		return strings.HasPrefix(cert, CertSecretPrefix)
    77  	})
    78  }
    79  
    80  // CleanCerts removes the certs and their associated secrets which hold a TLS certificate issued by cert-manager
    81  func CleanCerts(client kubernetes.Interface, certclient certclient.Interface, ns string, filter []Certificate) error {
    82  	allowed := make(map[string]bool)
    83  	for _, cert := range filter {
    84  		allowed[cert.Name] = true
    85  	}
    86  	return cleanCerts(client, certclient, ns, func(cert string) bool {
    87  		_, ok := allowed[cert]
    88  		return ok
    89  	})
    90  }
    91  
    92  func cleanCerts(client kubernetes.Interface, certclient certclient.Interface, ns string, allow func(string) bool) error {
    93  	certsClient := certclient.CertmanagerV1alpha1().Certificates(ns)
    94  	certsList, err := certsClient.List(metav1.ListOptions{})
    95  	if err != nil {
    96  		// there are no certificates to clean
    97  		return nil
    98  	}
    99  	for _, c := range certsList.Items {
   100  		if allow(c.GetName()) {
   101  			err := certsClient.Delete(c.GetName(), &metav1.DeleteOptions{})
   102  			if err != nil {
   103  				return errors.Wrapf(err, "deleting the cert %s/%s", ns, c.GetName())
   104  			}
   105  		}
   106  	}
   107  	// delete the tls related secrets so we dont reuse old ones when switching from http to https
   108  	secrets, err := client.CoreV1().Secrets(ns).List(metav1.ListOptions{})
   109  	if err != nil {
   110  		return errors.Wrapf(err, "listing the secrets in namespace %q", ns)
   111  	}
   112  	for _, s := range secrets.Items {
   113  		if allow(s.GetName()) {
   114  			err := client.CoreV1().Secrets(ns).Delete(s.Name, &metav1.DeleteOptions{})
   115  			if err != nil {
   116  				return errors.Wrapf(err, "deleting the tls secret %s/%s", ns, s.GetName())
   117  			}
   118  		}
   119  	}
   120  	return nil
   121  }
   122  
   123  // String returns the certificate information in a string format
   124  func (c Certificate) String() string {
   125  	return fmt.Sprintf("%s/%s", c.Namespace, c.Name)
   126  }
   127  
   128  // WatchCertificatesIssuedReady starts watching for ready certificate in the given namespace.
   129  // If the namespace is empty, it will watch the entire cluster. The caller can stop watching by cancelling the context.
   130  func WatchCertificatesIssuedReady(ctx context.Context, client certclient.Interface, ns string) (<-chan Certificate, error) {
   131  	watcher, err := client.CertmanagerV1alpha1().Certificates(ns).Watch(metav1.ListOptions{})
   132  	if err != nil {
   133  		return nil, errors.Wrapf(err, "watching certificates in namespace %q", ns)
   134  	}
   135  	results := make(chan Certificate)
   136  	go func() {
   137  		for {
   138  			select {
   139  			case <-ctx.Done():
   140  				watcher.Stop()
   141  				return
   142  			case e := <-watcher.ResultChan():
   143  				if e.Type == watch.Added || e.Type == watch.Modified {
   144  					cert, ok := e.Object.(*certmng.Certificate)
   145  					if ok {
   146  						if isCertReady(cert) {
   147  							result := Certificate{
   148  								Name:      cert.GetName(),
   149  								Namespace: cert.GetNamespace(),
   150  							}
   151  							results <- result
   152  						}
   153  					}
   154  				}
   155  			}
   156  		}
   157  	}()
   158  
   159  	return results, nil
   160  }
   161  
   162  func isCertReady(cert *certmng.Certificate) bool {
   163  	return certutil.CertificateHasCondition(cert, certmng.CertificateCondition{
   164  		Type:   certmng.CertificateConditionReady,
   165  		Status: certmng.ConditionTrue,
   166  	})
   167  }
   168  
   169  // GetIssuedReadyCertificates returns the current ready certificates in the given namespace
   170  func GetIssuedReadyCertificates(client certclient.Interface, ns string) ([]Certificate, error) {
   171  	certs := make([]Certificate, 0)
   172  	certsList, err := client.CertmanagerV1alpha1().Certificates(ns).List(metav1.ListOptions{})
   173  	if err != nil {
   174  		return certs, errors.Wrapf(err, "listing certificates in namespace %q", ns)
   175  	}
   176  	for _, c := range certsList.Items {
   177  		cert := c
   178  		if isCertReady(&cert) {
   179  			certs = append(certs, Certificate{
   180  				Name:      cert.GetName(),
   181  				Namespace: cert.GetNamespace(),
   182  			})
   183  		}
   184  	}
   185  	return certs, nil
   186  }
   187  
   188  // ToCertificates converts a list of services into a list of certificates. The certificate name is built from
   189  // the application label of the service.
   190  func ToCertificates(services []*v1.Service) []Certificate {
   191  	result := make([]Certificate, 0)
   192  	for _, svc := range services {
   193  		app := kubeservices.ServiceAppName(svc)
   194  		cert := CertSecretPrefix + app
   195  		ns := svc.GetNamespace()
   196  		result = append(result, Certificate{
   197  			Name:      cert,
   198  			Namespace: ns,
   199  		})
   200  	}
   201  	return result
   202  }