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 }