k8s.io/kubernetes@v1.29.3/pkg/controller/certificates/signer/signer.go (about) 1 /* 2 Copyright 2019 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 signer implements a CA signer that uses keys stored on local disk. 18 package signer 19 20 import ( 21 "context" 22 "crypto/x509" 23 "encoding/pem" 24 "fmt" 25 "time" 26 27 capi "k8s.io/api/certificates/v1" 28 capiv1beta1 "k8s.io/api/certificates/v1beta1" 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/apiserver/pkg/server/dynamiccertificates" 33 certificatesinformers "k8s.io/client-go/informers/certificates/v1" 34 clientset "k8s.io/client-go/kubernetes" 35 "k8s.io/client-go/util/certificate/csr" 36 capihelper "k8s.io/kubernetes/pkg/apis/certificates" 37 "k8s.io/kubernetes/pkg/controller/certificates" 38 "k8s.io/kubernetes/pkg/controller/certificates/authority" 39 ) 40 41 type CSRSigningController struct { 42 certificateController *certificates.CertificateController 43 dynamicCertReloader dynamiccertificates.ControllerRunner 44 } 45 46 func NewKubeletServingCSRSigningController( 47 ctx context.Context, 48 client clientset.Interface, 49 csrInformer certificatesinformers.CertificateSigningRequestInformer, 50 caFile, caKeyFile string, 51 certTTL time.Duration, 52 ) (*CSRSigningController, error) { 53 return NewCSRSigningController(ctx, "csrsigning-kubelet-serving", capi.KubeletServingSignerName, client, csrInformer, caFile, caKeyFile, certTTL) 54 } 55 56 func NewKubeletClientCSRSigningController( 57 ctx context.Context, 58 client clientset.Interface, 59 csrInformer certificatesinformers.CertificateSigningRequestInformer, 60 caFile, caKeyFile string, 61 certTTL time.Duration, 62 ) (*CSRSigningController, error) { 63 return NewCSRSigningController(ctx, "csrsigning-kubelet-client", capi.KubeAPIServerClientKubeletSignerName, client, csrInformer, caFile, caKeyFile, certTTL) 64 } 65 66 func NewKubeAPIServerClientCSRSigningController( 67 ctx context.Context, 68 client clientset.Interface, 69 csrInformer certificatesinformers.CertificateSigningRequestInformer, 70 caFile, caKeyFile string, 71 certTTL time.Duration, 72 ) (*CSRSigningController, error) { 73 return NewCSRSigningController(ctx, "csrsigning-kube-apiserver-client", capi.KubeAPIServerClientSignerName, client, csrInformer, caFile, caKeyFile, certTTL) 74 } 75 76 func NewLegacyUnknownCSRSigningController( 77 ctx context.Context, 78 client clientset.Interface, 79 csrInformer certificatesinformers.CertificateSigningRequestInformer, 80 caFile, caKeyFile string, 81 certTTL time.Duration, 82 ) (*CSRSigningController, error) { 83 return NewCSRSigningController(ctx, "csrsigning-legacy-unknown", capiv1beta1.LegacyUnknownSignerName, client, csrInformer, caFile, caKeyFile, certTTL) 84 } 85 86 func NewCSRSigningController( 87 ctx context.Context, 88 controllerName string, 89 signerName string, 90 client clientset.Interface, 91 csrInformer certificatesinformers.CertificateSigningRequestInformer, 92 caFile, caKeyFile string, 93 certTTL time.Duration, 94 ) (*CSRSigningController, error) { 95 signer, err := newSigner(signerName, caFile, caKeyFile, client, certTTL) 96 if err != nil { 97 return nil, err 98 } 99 100 return &CSRSigningController{ 101 certificateController: certificates.NewCertificateController( 102 ctx, 103 controllerName, 104 client, 105 csrInformer, 106 signer.handle, 107 ), 108 dynamicCertReloader: signer.caProvider.caLoader, 109 }, nil 110 } 111 112 // Run the main goroutine responsible for watching and syncing jobs. 113 func (c *CSRSigningController) Run(ctx context.Context, workers int) { 114 go c.dynamicCertReloader.Run(ctx, workers) 115 116 c.certificateController.Run(ctx, workers) 117 } 118 119 type isRequestForSignerFunc func(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) 120 121 type signer struct { 122 caProvider *caProvider 123 124 client clientset.Interface 125 certTTL time.Duration // max TTL; individual requests may request shorter certs by setting spec.expirationSeconds 126 127 signerName string 128 isRequestForSignerFn isRequestForSignerFunc 129 } 130 131 func newSigner(signerName, caFile, caKeyFile string, client clientset.Interface, certificateDuration time.Duration) (*signer, error) { 132 isRequestForSignerFn, err := getCSRVerificationFuncForSignerName(signerName) 133 if err != nil { 134 return nil, err 135 } 136 caProvider, err := newCAProvider(caFile, caKeyFile) 137 if err != nil { 138 return nil, err 139 } 140 141 ret := &signer{ 142 caProvider: caProvider, 143 client: client, 144 certTTL: certificateDuration, 145 signerName: signerName, 146 isRequestForSignerFn: isRequestForSignerFn, 147 } 148 return ret, nil 149 } 150 151 func (s *signer) handle(ctx context.Context, csr *capi.CertificateSigningRequest) error { 152 // Ignore unapproved or failed requests 153 if !certificates.IsCertificateRequestApproved(csr) || certificates.HasTrueCondition(csr, capi.CertificateFailed) { 154 return nil 155 } 156 157 // Fast-path to avoid any additional processing if the CSRs signerName does not match 158 if csr.Spec.SignerName != s.signerName { 159 return nil 160 } 161 162 x509cr, err := capihelper.ParseCSR(csr.Spec.Request) 163 if err != nil { 164 return fmt.Errorf("unable to parse csr %q: %v", csr.Name, err) 165 } 166 if recognized, err := s.isRequestForSignerFn(x509cr, csr.Spec.Usages, csr.Spec.SignerName); err != nil { 167 csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{ 168 Type: capi.CertificateFailed, 169 Status: v1.ConditionTrue, 170 Reason: "SignerValidationFailure", 171 Message: err.Error(), 172 LastUpdateTime: metav1.Now(), 173 }) 174 _, err = s.client.CertificatesV1().CertificateSigningRequests().UpdateStatus(ctx, csr, metav1.UpdateOptions{}) 175 if err != nil { 176 return fmt.Errorf("error adding failure condition for csr: %v", err) 177 } 178 return nil 179 } else if !recognized { 180 // Ignore requests for kubernetes.io signerNames we don't recognize 181 return nil 182 } 183 cert, err := s.sign(x509cr, csr.Spec.Usages, csr.Spec.ExpirationSeconds, nil) 184 if err != nil { 185 return fmt.Errorf("error auto signing csr: %v", err) 186 } 187 csr.Status.Certificate = cert 188 _, err = s.client.CertificatesV1().CertificateSigningRequests().UpdateStatus(ctx, csr, metav1.UpdateOptions{}) 189 if err != nil { 190 return fmt.Errorf("error updating signature for csr: %v", err) 191 } 192 return nil 193 } 194 195 func (s *signer) sign(x509cr *x509.CertificateRequest, usages []capi.KeyUsage, expirationSeconds *int32, now func() time.Time) ([]byte, error) { 196 currCA, err := s.caProvider.currentCA() 197 if err != nil { 198 return nil, err 199 } 200 der, err := currCA.Sign(x509cr.Raw, authority.PermissiveSigningPolicy{ 201 TTL: s.duration(expirationSeconds), 202 Usages: usages, 203 Backdate: 5 * time.Minute, // this must always be less than the minimum TTL requested by a user (see sanity check requestedDuration below) 204 Short: 8 * time.Hour, // 5 minutes of backdating is roughly 1% of 8 hours 205 Now: now, 206 }) 207 if err != nil { 208 return nil, err 209 } 210 return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), nil 211 } 212 213 func (s *signer) duration(expirationSeconds *int32) time.Duration { 214 if expirationSeconds == nil { 215 return s.certTTL 216 } 217 218 // honor requested duration is if it is less than the default TTL 219 // use 10 min (2x hard coded backdate above) as a sanity check lower bound 220 const min = 10 * time.Minute 221 switch requestedDuration := csr.ExpirationSecondsToDuration(*expirationSeconds); { 222 case requestedDuration > s.certTTL: 223 return s.certTTL 224 225 case requestedDuration < min: 226 return min 227 228 default: 229 return requestedDuration 230 } 231 } 232 233 // getCSRVerificationFuncForSignerName is a function that provides reliable mapping of signer names to verification so that 234 // we don't have accidents with wiring at some later date. 235 func getCSRVerificationFuncForSignerName(signerName string) (isRequestForSignerFunc, error) { 236 switch signerName { 237 case capi.KubeletServingSignerName: 238 return isKubeletServing, nil 239 case capi.KubeAPIServerClientKubeletSignerName: 240 return isKubeletClient, nil 241 case capi.KubeAPIServerClientSignerName: 242 return isKubeAPIServerClient, nil 243 case capiv1beta1.LegacyUnknownSignerName: 244 return isLegacyUnknown, nil 245 default: 246 // TODO type this error so that a different reporting loop (one without a signing cert), can mark 247 // CSRs with unknown kube signers as terminal if we wish. This largely depends on how tightly we want to control 248 // our signerNames. 249 return nil, fmt.Errorf("unrecognized signerName: %q", signerName) 250 } 251 } 252 253 func isKubeletServing(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) { 254 if signerName != capi.KubeletServingSignerName { 255 return false, nil 256 } 257 return true, capihelper.ValidateKubeletServingCSR(req, usagesToSet(usages)) 258 } 259 260 func isKubeletClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) { 261 if signerName != capi.KubeAPIServerClientKubeletSignerName { 262 return false, nil 263 } 264 return true, capihelper.ValidateKubeletClientCSR(req, usagesToSet(usages)) 265 } 266 267 func isKubeAPIServerClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) { 268 if signerName != capi.KubeAPIServerClientSignerName { 269 return false, nil 270 } 271 return true, validAPIServerClientUsages(usages) 272 } 273 274 func isLegacyUnknown(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) { 275 if signerName != capiv1beta1.LegacyUnknownSignerName { 276 return false, nil 277 } 278 // No restrictions are applied to the legacy-unknown signerName to 279 // maintain backward compatibility in v1. 280 return true, nil 281 } 282 283 func validAPIServerClientUsages(usages []capi.KeyUsage) error { 284 hasClientAuth := false 285 for _, u := range usages { 286 switch u { 287 // these usages are optional 288 case capi.UsageDigitalSignature, capi.UsageKeyEncipherment: 289 case capi.UsageClientAuth: 290 hasClientAuth = true 291 default: 292 return fmt.Errorf("invalid usage for client certificate: %s", u) 293 } 294 } 295 if !hasClientAuth { 296 return fmt.Errorf("missing required usage for client certificate: %s", capi.UsageClientAuth) 297 } 298 return nil 299 } 300 301 func usagesToSet(usages []capi.KeyUsage) sets.String { 302 result := sets.NewString() 303 for _, usage := range usages { 304 result.Insert(string(usage)) 305 } 306 return result 307 }