k8s.io/kubernetes@v1.29.3/pkg/controller/certificates/approver/sarapprove.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package approver implements an automated approver for kubelet certificates. 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 // NewCSRApprovingController creates a new CSRApprovingController. 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 }