cleaner.go 7.2 KB

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