1
16
17
18
19
20
21 package cleaner
22
23 import (
24 "context"
25 "crypto/x509"
26 "encoding/pem"
27 "fmt"
28 "time"
29
30 "k8s.io/klog/v2"
31
32 capi "k8s.io/api/certificates/v1"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/labels"
35 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
36 "k8s.io/apimachinery/pkg/util/wait"
37 certificatesinformers "k8s.io/client-go/informers/certificates/v1"
38 csrclient "k8s.io/client-go/kubernetes/typed/certificates/v1"
39 certificateslisters "k8s.io/client-go/listers/certificates/v1"
40 )
41
42 const (
43
44
45 pollingInterval = 1 * time.Hour
46
47
48 approvedExpiration = 1 * time.Hour
49 deniedExpiration = 1 * time.Hour
50 pendingExpiration = 24 * time.Hour
51 )
52
53
54
55
56
57
58
59
60
61
62 type CSRCleanerController struct {
63 csrClient csrclient.CertificateSigningRequestInterface
64 csrLister certificateslisters.CertificateSigningRequestLister
65 }
66
67
68 func NewCSRCleanerController(
69 csrClient csrclient.CertificateSigningRequestInterface,
70 csrInformer certificatesinformers.CertificateSigningRequestInformer,
71 ) *CSRCleanerController {
72 return &CSRCleanerController{
73 csrClient: csrClient,
74 csrLister: csrInformer.Lister(),
75 }
76 }
77
78
79 func (ccc *CSRCleanerController) Run(ctx context.Context, workers int) {
80 defer utilruntime.HandleCrash()
81
82 logger := klog.FromContext(ctx)
83 logger.Info("Starting CSR cleaner controller")
84 defer logger.Info("Shutting down CSR cleaner controller")
85
86 for i := 0; i < workers; i++ {
87 go wait.UntilWithContext(ctx, ccc.worker, pollingInterval)
88 }
89
90 <-ctx.Done()
91 }
92
93
94 func (ccc *CSRCleanerController) worker(ctx context.Context) {
95 logger := klog.FromContext(ctx)
96 csrs, err := ccc.csrLister.List(labels.Everything())
97 if err != nil {
98 logger.Error(err, "Unable to list CSRs")
99 return
100 }
101 for _, csr := range csrs {
102 if err := ccc.handle(ctx, csr); err != nil {
103 logger.Error(err, "Error while attempting to clean CSR", "csr", csr.Name)
104 }
105 }
106 }
107
108 func (ccc *CSRCleanerController) handle(ctx context.Context, csr *capi.CertificateSigningRequest) error {
109 logger := klog.FromContext(ctx)
110 if isIssuedPastDeadline(logger, csr) || isDeniedPastDeadline(logger, csr) || isFailedPastDeadline(logger, csr) || isPendingPastDeadline(logger, csr) || isIssuedExpired(logger, csr) {
111 if err := ccc.csrClient.Delete(ctx, csr.Name, metav1.DeleteOptions{}); err != nil {
112 return fmt.Errorf("unable to delete CSR %q: %v", csr.Name, err)
113 }
114 }
115 return nil
116 }
117
118
119
120 func isIssuedExpired(logger klog.Logger, csr *capi.CertificateSigningRequest) bool {
121 for _, c := range csr.Status.Conditions {
122 if c.Type == capi.CertificateApproved && isIssued(csr) && isExpired(csr) {
123 logger.Info("Cleaning CSR as the associated certificate is expired.", "csr", csr.Name)
124 return true
125 }
126 }
127 return false
128 }
129
130
131
132
133 func isPendingPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool {
134
135
136 if len(csr.Status.Conditions) == 0 && isOlderThan(csr.CreationTimestamp, pendingExpiration) {
137 logger.Info("Cleaning CSR as it is more than pendingExpiration duration old and unhandled.", "csr", csr.Name, "pendingExpiration", pendingExpiration)
138 return true
139 }
140 return false
141 }
142
143
144
145
146 func isDeniedPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool {
147 for _, c := range csr.Status.Conditions {
148 if c.Type == capi.CertificateDenied && isOlderThan(c.LastUpdateTime, deniedExpiration) {
149 logger.Info("Cleaning CSR as it is more than deniedExpiration duration old and denied.", "csr", csr.Name, "deniedExpiration", deniedExpiration)
150 return true
151 }
152 }
153 return false
154 }
155
156
157
158
159 func isFailedPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool {
160 for _, c := range csr.Status.Conditions {
161 if c.Type == capi.CertificateFailed && isOlderThan(c.LastUpdateTime, deniedExpiration) {
162 logger.Info("Cleaning CSR as it is more than deniedExpiration duration old and failed.", "csr", csr.Name, "deniedExpiration", deniedExpiration)
163 return true
164 }
165 }
166 return false
167 }
168
169
170
171
172 func isIssuedPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool {
173 for _, c := range csr.Status.Conditions {
174 if c.Type == capi.CertificateApproved && isIssued(csr) && isOlderThan(c.LastUpdateTime, approvedExpiration) {
175 logger.Info("Cleaning CSR as it is more than approvedExpiration duration old and approved.", "csr", csr.Name, "approvedExpiration", approvedExpiration)
176 return true
177 }
178 }
179 return false
180 }
181
182
183 func isOlderThan(t metav1.Time, d time.Duration) bool {
184 return !t.IsZero() && t.Sub(time.Now()) < -1*d
185 }
186
187
188
189
190 func isIssued(csr *capi.CertificateSigningRequest) bool {
191 return len(csr.Status.Certificate) > 0
192 }
193
194
195
196 func isExpired(csr *capi.CertificateSigningRequest) bool {
197 if len(csr.Status.Certificate) == 0 {
198 return false
199 }
200 block, _ := pem.Decode(csr.Status.Certificate)
201 if block == nil {
202 return false
203 }
204 certs, err := x509.ParseCertificates(block.Bytes)
205 if err != nil {
206 return false
207 }
208 if len(certs) == 0 {
209 return false
210 }
211 return time.Now().After(certs[0].NotAfter)
212 }
213
View as plain text