cleaner.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package cleaner implements an automated cleaner that does garbage collection
  14. // on CSRs that meet specific criteria. With automated CSR requests and
  15. // automated approvals, the volume of CSRs only increases over time, at a rapid
  16. // rate if the certificate duration is short.
  17. package cleaner
  18. import (
  19. "context"
  20. "crypto/x509"
  21. "encoding/pem"
  22. "fmt"
  23. "time"
  24. "k8s.io/klog"
  25. capi "k8s.io/api/certificates/v1beta1"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/labels"
  28. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  29. "k8s.io/apimachinery/pkg/util/wait"
  30. certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
  31. csrclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
  32. certificateslisters "k8s.io/client-go/listers/certificates/v1beta1"
  33. )
  34. const (
  35. // The interval to list all CSRs and check each one against the criteria to
  36. // automatically clean it up.
  37. pollingInterval = 1 * time.Hour
  38. // The time periods after which these different CSR statuses should be
  39. // cleaned up.
  40. approvedExpiration = 1 * time.Hour
  41. deniedExpiration = 1 * time.Hour
  42. pendingExpiration = 24 * time.Hour
  43. )
  44. // CSRCleanerController is a controller that garbage collects old certificate
  45. // signing requests (CSRs). Since there are mechanisms that automatically
  46. // create CSRs, and mechanisms that automatically approve CSRs, in order to
  47. // prevent a build up of CSRs over time, it is necessary to GC them. CSRs will
  48. // be removed if they meet one of the following criteria: the CSR is Approved
  49. // with a certificate and is old enough to be past the GC issued deadline, the
  50. // CSR is denied and is old enough to be past the GC denied deadline, the CSR
  51. // is Pending and is old enough to be past the GC pending deadline, the CSR is
  52. // approved with a certificate and the certificate is expired.
  53. type CSRCleanerController struct {
  54. csrClient csrclient.CertificateSigningRequestInterface
  55. csrLister certificateslisters.CertificateSigningRequestLister
  56. }
  57. // NewCSRCleanerController creates a new CSRCleanerController.
  58. func NewCSRCleanerController(
  59. csrClient csrclient.CertificateSigningRequestInterface,
  60. csrInformer certificatesinformers.CertificateSigningRequestInformer,
  61. ) *CSRCleanerController {
  62. return &CSRCleanerController{
  63. csrClient: csrClient,
  64. csrLister: csrInformer.Lister(),
  65. }
  66. }
  67. // Run the main goroutine responsible for watching and syncing jobs.
  68. func (ccc *CSRCleanerController) Run(workers int, stopCh <-chan struct{}) {
  69. defer utilruntime.HandleCrash()
  70. klog.Infof("Starting CSR cleaner controller")
  71. defer klog.Infof("Shutting down CSR cleaner controller")
  72. for i := 0; i < workers; i++ {
  73. go wait.Until(ccc.worker, pollingInterval, stopCh)
  74. }
  75. <-stopCh
  76. }
  77. // worker runs a thread that dequeues CSRs, handles them, and marks them done.
  78. func (ccc *CSRCleanerController) worker() {
  79. csrs, err := ccc.csrLister.List(labels.Everything())
  80. if err != nil {
  81. klog.Errorf("Unable to list CSRs: %v", err)
  82. return
  83. }
  84. for _, csr := range csrs {
  85. if err := ccc.handle(csr); err != nil {
  86. klog.Errorf("Error while attempting to clean CSR %q: %v", csr.Name, err)
  87. }
  88. }
  89. }
  90. func (ccc *CSRCleanerController) handle(csr *capi.CertificateSigningRequest) error {
  91. isIssuedExpired, err := isIssuedExpired(csr)
  92. if err != nil {
  93. return err
  94. }
  95. if isIssuedPastDeadline(csr) || isDeniedPastDeadline(csr) || isPendingPastDeadline(csr) || isIssuedExpired {
  96. if err := ccc.csrClient.Delete(context.TODO(), csr.Name, nil); err != nil {
  97. return fmt.Errorf("unable to delete CSR %q: %v", csr.Name, err)
  98. }
  99. }
  100. return nil
  101. }
  102. // isIssuedExpired checks if the CSR has been issued a certificate and if the
  103. // expiration of the certificate (the NotAfter value) has passed.
  104. func isIssuedExpired(csr *capi.CertificateSigningRequest) (bool, error) {
  105. isExpired, err := isExpired(csr)
  106. if err != nil {
  107. return false, err
  108. }
  109. for _, c := range csr.Status.Conditions {
  110. if c.Type == capi.CertificateApproved && isIssued(csr) && isExpired {
  111. klog.Infof("Cleaning CSR %q as the associated certificate is expired.", csr.Name)
  112. return true, nil
  113. }
  114. }
  115. return false, nil
  116. }
  117. // isPendingPastDeadline checks if the certificate has a Pending status and the
  118. // creation time of the CSR is passed the deadline that pending requests are
  119. // maintained for.
  120. func isPendingPastDeadline(csr *capi.CertificateSigningRequest) bool {
  121. // If there are no Conditions on the status, the CSR will appear via
  122. // `kubectl` as `Pending`.
  123. if len(csr.Status.Conditions) == 0 && isOlderThan(csr.CreationTimestamp, pendingExpiration) {
  124. klog.Infof("Cleaning CSR %q as it is more than %v old and unhandled.", csr.Name, pendingExpiration)
  125. return true
  126. }
  127. return false
  128. }
  129. // isDeniedPastDeadline checks if the certificate has a Denied status and the
  130. // creation time of the CSR is passed the deadline that denied requests are
  131. // maintained for.
  132. func isDeniedPastDeadline(csr *capi.CertificateSigningRequest) bool {
  133. for _, c := range csr.Status.Conditions {
  134. if c.Type == capi.CertificateDenied && isOlderThan(c.LastUpdateTime, deniedExpiration) {
  135. klog.Infof("Cleaning CSR %q as it is more than %v old and denied.", csr.Name, deniedExpiration)
  136. return true
  137. }
  138. }
  139. return false
  140. }
  141. // isIssuedPastDeadline checks if the certificate has an Issued status and the
  142. // creation time of the CSR is passed the deadline that issued requests are
  143. // maintained for.
  144. func isIssuedPastDeadline(csr *capi.CertificateSigningRequest) bool {
  145. for _, c := range csr.Status.Conditions {
  146. if c.Type == capi.CertificateApproved && isIssued(csr) && isOlderThan(c.LastUpdateTime, approvedExpiration) {
  147. klog.Infof("Cleaning CSR %q as it is more than %v old and approved.", csr.Name, approvedExpiration)
  148. return true
  149. }
  150. }
  151. return false
  152. }
  153. // isOlderThan checks that t is a non-zero time after time.Now() + d.
  154. func isOlderThan(t metav1.Time, d time.Duration) bool {
  155. return !t.IsZero() && t.Sub(time.Now()) < -1*d
  156. }
  157. // isIssued checks if the CSR has `Issued` status. There is no explicit
  158. // 'Issued' status. Implicitly, if there is a certificate associated with the
  159. // CSR, the CSR statuses that are visible via `kubectl` will include 'Issued'.
  160. func isIssued(csr *capi.CertificateSigningRequest) bool {
  161. return csr.Status.Certificate != nil
  162. }
  163. // isExpired checks if the CSR has a certificate and the date in the `NotAfter`
  164. // field has gone by.
  165. func isExpired(csr *capi.CertificateSigningRequest) (bool, error) {
  166. if csr.Status.Certificate == nil {
  167. return false, nil
  168. }
  169. block, _ := pem.Decode(csr.Status.Certificate)
  170. if block == nil {
  171. return false, fmt.Errorf("expected the certificate associated with the CSR to be PEM encoded")
  172. }
  173. certs, err := x509.ParseCertificates(block.Bytes)
  174. if err != nil {
  175. return false, fmt.Errorf("unable to parse certificate data: %v", err)
  176. }
  177. return time.Now().After(certs[0].NotAfter), nil
  178. }