123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- /*
- 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"
- 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"
- )
- 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
- }
- // NewCSRApprovingController creates a new CSRApprovingController.
- func NewCSRApprovingController(client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) *certificates.CertificateController {
- 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.",
- },
- }
- 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.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(csr)
- if err != nil {
- return fmt.Errorf("error updating approval for csr: %v", err)
- }
- return nil
- }
- }
- if len(tried) != 0 {
- return certificates.IgnorableError("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,
- UID: csr.Spec.UID,
- 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
- }
|