1
16
17
18 package approver
19
20 import (
21 "context"
22 "crypto/x509"
23 "fmt"
24
25 authorization "k8s.io/api/authorization/v1"
26 capi "k8s.io/api/certificates/v1"
27 corev1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/util/sets"
30 certificatesinformers "k8s.io/client-go/informers/certificates/v1"
31 clientset "k8s.io/client-go/kubernetes"
32 capihelper "k8s.io/kubernetes/pkg/apis/certificates"
33 "k8s.io/kubernetes/pkg/controller/certificates"
34 )
35
36 type csrRecognizer struct {
37 recognize func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool
38 permission authorization.ResourceAttributes
39 successMessage string
40 }
41
42 type sarApprover struct {
43 client clientset.Interface
44 recognizers []csrRecognizer
45 }
46
47
48 func NewCSRApprovingController(ctx context.Context, client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) *certificates.CertificateController {
49 approver := &sarApprover{
50 client: client,
51 recognizers: recognizers(),
52 }
53 return certificates.NewCertificateController(
54 ctx,
55 "csrapproving",
56 client,
57 csrInformer,
58 approver.handle,
59 )
60 }
61
62 func recognizers() []csrRecognizer {
63 recognizers := []csrRecognizer{
64 {
65 recognize: isSelfNodeClientCert,
66 permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeclient", Version: "*"},
67 successMessage: "Auto approving self kubelet client certificate after SubjectAccessReview.",
68 },
69 {
70 recognize: isNodeClientCert,
71 permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "nodeclient", Version: "*"},
72 successMessage: "Auto approving kubelet client certificate after SubjectAccessReview.",
73 },
74 }
75 return recognizers
76 }
77
78 func (a *sarApprover) handle(ctx context.Context, csr *capi.CertificateSigningRequest) error {
79 if len(csr.Status.Certificate) != 0 {
80 return nil
81 }
82 if approved, denied := certificates.GetCertApprovalCondition(&csr.Status); approved || denied {
83 return nil
84 }
85 x509cr, err := capihelper.ParseCSR(csr.Spec.Request)
86 if err != nil {
87 return fmt.Errorf("unable to parse csr %q: %v", csr.Name, err)
88 }
89
90 tried := []string{}
91
92 for _, r := range a.recognizers {
93 if !r.recognize(csr, x509cr) {
94 continue
95 }
96
97 tried = append(tried, r.permission.Subresource)
98
99 approved, err := a.authorize(ctx, csr, r.permission)
100 if err != nil {
101 return err
102 }
103 if approved {
104 appendApprovalCondition(csr, r.successMessage)
105 _, err = a.client.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, csr.Name, csr, metav1.UpdateOptions{})
106 if err != nil {
107 return fmt.Errorf("error updating approval for csr: %v", err)
108 }
109 return nil
110 }
111 }
112
113 if len(tried) != 0 {
114 return certificates.IgnorableError("recognized csr %q as %v but subject access review was not approved", csr.Name, tried)
115 }
116
117 return nil
118 }
119
120 func (a *sarApprover) authorize(ctx context.Context, csr *capi.CertificateSigningRequest, rattrs authorization.ResourceAttributes) (bool, error) {
121 extra := make(map[string]authorization.ExtraValue)
122 for k, v := range csr.Spec.Extra {
123 extra[k] = authorization.ExtraValue(v)
124 }
125
126 sar := &authorization.SubjectAccessReview{
127 Spec: authorization.SubjectAccessReviewSpec{
128 User: csr.Spec.Username,
129 UID: csr.Spec.UID,
130 Groups: csr.Spec.Groups,
131 Extra: extra,
132 ResourceAttributes: &rattrs,
133 },
134 }
135 sar, err := a.client.AuthorizationV1().SubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
136 if err != nil {
137 return false, err
138 }
139 return sar.Status.Allowed, nil
140 }
141
142 func appendApprovalCondition(csr *capi.CertificateSigningRequest, message string) {
143 csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
144 Type: capi.CertificateApproved,
145 Status: corev1.ConditionTrue,
146 Reason: "AutoApproved",
147 Message: message,
148 })
149 }
150
151 func isNodeClientCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
152 if csr.Spec.SignerName != capi.KubeAPIServerClientKubeletSignerName {
153 return false
154 }
155 return capihelper.IsKubeletClientCSR(x509cr, usagesToSet(csr.Spec.Usages))
156 }
157
158 func isSelfNodeClientCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
159 if csr.Spec.Username != x509cr.Subject.CommonName {
160 return false
161 }
162 return isNodeClientCert(csr, x509cr)
163 }
164
165 func usagesToSet(usages []capi.KeyUsage) sets.String {
166 result := sets.NewString()
167 for _, usage := range usages {
168 result.Insert(string(usage))
169 }
170 return result
171 }
172
View as plain text