mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			228 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2016 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
// Package approver implements an automated approver for kubelet certificates.
 | 
						|
package approver
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/x509"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	authorization "k8s.io/api/authorization/v1beta1"
 | 
						|
	capi "k8s.io/api/certificates/v1beta1"
 | 
						|
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						|
	certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
 | 
						|
	clientset "k8s.io/client-go/kubernetes"
 | 
						|
	k8s_certificates_v1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
 | 
						|
	"k8s.io/kubernetes/pkg/controller/certificates"
 | 
						|
	"k8s.io/kubernetes/pkg/features"
 | 
						|
)
 | 
						|
 | 
						|
type csrRecognizer struct {
 | 
						|
	recognize      func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool
 | 
						|
	permission     authorization.ResourceAttributes
 | 
						|
	successMessage string
 | 
						|
}
 | 
						|
 | 
						|
type sarApprover struct {
 | 
						|
	client      clientset.Interface
 | 
						|
	recognizers []csrRecognizer
 | 
						|
}
 | 
						|
 | 
						|
func NewCSRApprovingController(client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) (*certificates.CertificateController, error) {
 | 
						|
	approver := &sarApprover{
 | 
						|
		client:      client,
 | 
						|
		recognizers: recognizers(),
 | 
						|
	}
 | 
						|
	return certificates.NewCertificateController(
 | 
						|
		client,
 | 
						|
		csrInformer,
 | 
						|
		approver.handle,
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func recognizers() []csrRecognizer {
 | 
						|
	recognizers := []csrRecognizer{
 | 
						|
		{
 | 
						|
			recognize:      isSelfNodeClientCert,
 | 
						|
			permission:     authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeclient"},
 | 
						|
			successMessage: "Auto approving self kubelet client certificate after SubjectAccessReview.",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			recognize:      isNodeClientCert,
 | 
						|
			permission:     authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "nodeclient"},
 | 
						|
			successMessage: "Auto approving kubelet client certificate after SubjectAccessReview.",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	if utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
 | 
						|
		recognizers = append(recognizers, csrRecognizer{
 | 
						|
			recognize:      isSelfNodeServerCert,
 | 
						|
			permission:     authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeserver"},
 | 
						|
			successMessage: "Auto approving self kubelet server certificate after SubjectAccessReview.",
 | 
						|
		})
 | 
						|
	}
 | 
						|
	return recognizers
 | 
						|
}
 | 
						|
 | 
						|
func (a *sarApprover) handle(csr *capi.CertificateSigningRequest) error {
 | 
						|
	if len(csr.Status.Certificate) != 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if approved, denied := certificates.GetCertApprovalCondition(&csr.Status); approved || denied {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	x509cr, err := k8s_certificates_v1beta1.ParseCSR(csr)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("unable to parse csr %q: %v", csr.Name, err)
 | 
						|
	}
 | 
						|
 | 
						|
	tried := []string{}
 | 
						|
 | 
						|
	for _, r := range a.recognizers {
 | 
						|
		if !r.recognize(csr, x509cr) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		tried = append(tried, r.permission.Subresource)
 | 
						|
 | 
						|
		approved, err := a.authorize(csr, r.permission)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if approved {
 | 
						|
			appendApprovalCondition(csr, r.successMessage)
 | 
						|
			_, err = a.client.Certificates().CertificateSigningRequests().UpdateApproval(csr)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("error updating approval for csr: %v", err)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(tried) != 0 {
 | 
						|
		return fmt.Errorf("recognized csr %q as %v but subject access review was not approved", csr.Name, tried)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (a *sarApprover) authorize(csr *capi.CertificateSigningRequest, rattrs authorization.ResourceAttributes) (bool, error) {
 | 
						|
	extra := make(map[string]authorization.ExtraValue)
 | 
						|
	for k, v := range csr.Spec.Extra {
 | 
						|
		extra[k] = authorization.ExtraValue(v)
 | 
						|
	}
 | 
						|
 | 
						|
	sar := &authorization.SubjectAccessReview{
 | 
						|
		Spec: authorization.SubjectAccessReviewSpec{
 | 
						|
			User:               csr.Spec.Username,
 | 
						|
			Groups:             csr.Spec.Groups,
 | 
						|
			Extra:              extra,
 | 
						|
			ResourceAttributes: &rattrs,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	sar, err := a.client.AuthorizationV1beta1().SubjectAccessReviews().Create(sar)
 | 
						|
	if err != nil {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
	return sar.Status.Allowed, nil
 | 
						|
}
 | 
						|
 | 
						|
func appendApprovalCondition(csr *capi.CertificateSigningRequest, message string) {
 | 
						|
	csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
 | 
						|
		Type:    capi.CertificateApproved,
 | 
						|
		Reason:  "AutoApproved",
 | 
						|
		Message: message,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func hasExactUsages(csr *capi.CertificateSigningRequest, usages []capi.KeyUsage) bool {
 | 
						|
	if len(usages) != len(csr.Spec.Usages) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	usageMap := map[capi.KeyUsage]struct{}{}
 | 
						|
	for _, u := range usages {
 | 
						|
		usageMap[u] = struct{}{}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, u := range csr.Spec.Usages {
 | 
						|
		if _, ok := usageMap[u]; !ok {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
var kubeletClientUsages = []capi.KeyUsage{
 | 
						|
	capi.UsageKeyEncipherment,
 | 
						|
	capi.UsageDigitalSignature,
 | 
						|
	capi.UsageClientAuth,
 | 
						|
}
 | 
						|
 | 
						|
func isNodeClientCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
 | 
						|
	if !reflect.DeepEqual([]string{"system:nodes"}, x509cr.Subject.Organization) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if (len(x509cr.DNSNames) > 0) || (len(x509cr.EmailAddresses) > 0) || (len(x509cr.IPAddresses) > 0) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if !hasExactUsages(csr, kubeletClientUsages) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func isSelfNodeClientCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
 | 
						|
	if !isNodeClientCert(csr, x509cr) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if csr.Spec.Username != x509cr.Subject.CommonName {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
var kubeletServerUsages = []capi.KeyUsage{
 | 
						|
	capi.UsageKeyEncipherment,
 | 
						|
	capi.UsageDigitalSignature,
 | 
						|
	capi.UsageServerAuth,
 | 
						|
}
 | 
						|
 | 
						|
func isSelfNodeServerCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
 | 
						|
	if !reflect.DeepEqual([]string{"system:nodes"}, x509cr.Subject.Organization) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if len(x509cr.DNSNames) == 0 || len(x509cr.IPAddresses) == 0 {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if !hasExactUsages(csr, kubeletServerUsages) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if csr.Spec.Username != x509cr.Subject.CommonName {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 |