From 9f379df76b664adf7eda4400d44fdbb8b2e3fd0b Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Mon, 8 Aug 2016 15:36:10 -0700 Subject: [PATCH] add an option to controller-manager to auto approve all CSRs --- .../app/controllermanager.go | 1 + .../app/options/options.go | 1 + hack/verify-flags/known-flags.txt | 1 + pkg/apis/componentconfig/types.go | 6 ++ pkg/controller/certificates/controller.go | 67 +++++++++++++++++-- .../certificates/controller_utils.go | 17 +++-- 6 files changed, 84 insertions(+), 9 deletions(-) diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 6f61be28f74..2ebbc0b8fd2 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -468,6 +468,7 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig resyncPeriod, s.ClusterSigningCertFile, s.ClusterSigningKeyFile, + s.ApproveAllKubeletCSRsForGroup, ) if err != nil { glog.Errorf("Failed to start certificate controller: %v", err) diff --git a/cmd/kube-controller-manager/app/options/options.go b/cmd/kube-controller-manager/app/options/options.go index b557f170c2f..5011f96878f 100644 --- a/cmd/kube-controller-manager/app/options/options.go +++ b/cmd/kube-controller-manager/app/options/options.go @@ -159,6 +159,7 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.ServiceAccountKeyFile, "service-account-private-key-file", s.ServiceAccountKeyFile, "Filename containing a PEM-encoded private RSA key used to sign service account tokens.") fs.StringVar(&s.ClusterSigningCertFile, "cluster-signing-cert-file", s.ClusterSigningCertFile, "Filename containing a PEM-encoded X509 CA certificate used to issue cluster-scoped certificates") fs.StringVar(&s.ClusterSigningKeyFile, "cluster-signing-key-file", s.ClusterSigningKeyFile, "Filename containing a PEM-encoded RSA or ECDSA private key used to sign cluster-scoped certificates") + fs.StringVar(&s.ApproveAllKubeletCSRsForGroup, "insecure-experimental-approve-all-kubelet-csrs-for-group", s.ApproveAllKubeletCSRsForGroup, "The group for which the controller-manager will auto approve all CSRs for kubelet client certificates.") fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster") fs.StringVar(&s.ClusterCIDR, "cluster-cidr", s.ClusterCIDR, "CIDR Range for Pods in cluster.") diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index f3a669c1359..e0f1b41a398 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -229,6 +229,7 @@ include-extended-apis included-types-overrides input-base input-dirs +insecure-experimental-approve-all-kubelet-csrs-for-group insecure-bind-address insecure-port insecure-skip-tls-verify diff --git a/pkg/apis/componentconfig/types.go b/pkg/apis/componentconfig/types.go index 15f838e4844..e84329ca7fd 100644 --- a/pkg/apis/componentconfig/types.go +++ b/pkg/apis/componentconfig/types.go @@ -591,6 +591,12 @@ type KubeControllerManagerConfiguration struct { // clusterSigningCertFile is the filename containing a PEM-encoded // RSA or ECDSA private key used to issue cluster-scoped certificates ClusterSigningKeyFile string `json:"clusterSigningKeyFile"` + // approveAllKubeletCSRs tells the CSR controller to approve all CSRs originating + // from the kubelet bootstrapping group automatically. + // WARNING: this grants all users with access to the certificates API group + // the ability to create credentials for any user that has access to the boostrapping + // user's credentials. + ApproveAllKubeletCSRsForGroup string `json:"approveAllKubeletCSRsForGroup"` // enableProfiling enables profiling via web interface host:port/debug/pprof/ EnableProfiling bool `json:"enableProfiling"` // clusterName is the instance prefix for the cluster. diff --git a/pkg/controller/certificates/controller.go b/pkg/controller/certificates/controller.go index 029e9d52be6..05f654c2134 100644 --- a/pkg/controller/certificates/controller.go +++ b/pkg/controller/certificates/controller.go @@ -17,12 +17,11 @@ limitations under the License. package certificates import ( + "fmt" + "reflect" + "strings" "time" - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/signer" - "github.com/cloudflare/cfssl/signer/local" - "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/client/cache" @@ -32,10 +31,16 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/framework" "k8s.io/kubernetes/pkg/runtime" + utilcertificates "k8s.io/kubernetes/pkg/util/certificates" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/util/workqueue" "k8s.io/kubernetes/pkg/watch" + + "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/signer" + "github.com/cloudflare/cfssl/signer/local" + "github.com/golang/glog" ) type CertificateController struct { @@ -49,12 +54,14 @@ type CertificateController struct { updateHandler func(csr *certificates.CertificateSigningRequest) error syncHandler func(csrKey string) error + approveAllKubeletCSRsForGroup string + signer *local.Signer queue *workqueue.Type } -func NewCertificateController(kubeClient clientset.Interface, syncPeriod time.Duration, caCertFile, caKeyFile string) (*CertificateController, error) { +func NewCertificateController(kubeClient clientset.Interface, syncPeriod time.Duration, caCertFile, caKeyFile string, approveAllKubeletCSRsForGroup string) (*CertificateController, error) { // Send events to the apiserver eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) @@ -74,6 +81,7 @@ func NewCertificateController(kubeClient clientset.Interface, syncPeriod time.Du kubeClient: kubeClient, queue: workqueue.New(), signer: ca, + approveAllKubeletCSRsForGroup: approveAllKubeletCSRsForGroup, } // Manage the addition/update of certificate requests @@ -181,6 +189,11 @@ func (cc *CertificateController) maybeSignCertificate(key string) error { } csr := obj.(*certificates.CertificateSigningRequest) + csr, err = cc.maybeAutoApproveCSR(csr) + if err != nil { + return fmt.Errorf("error auto approving csr: %v", err) + } + // At this point, the controller needs to: // 1. Check the approval conditions // 2. Generate a signed certificate @@ -198,3 +211,47 @@ func (cc *CertificateController) maybeSignCertificate(key string) error { return cc.updateCertificateRequestStatus(csr) } + +func (cc *CertificateController) maybeAutoApproveCSR(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) { + // short-circuit if we're not auto-approving + if cc.approveAllKubeletCSRsForGroup == "" { + return csr, nil + } + // short-circuit if we're already approved or denied + if approved, denied := getCertApprovalCondition(&csr.Status); approved || denied { + return csr, nil + } + + isKubeletBootstrapGroup := false + for _, g := range csr.Spec.Groups { + if g == cc.approveAllKubeletCSRsForGroup { + isKubeletBootstrapGroup = true + break + } + } + if !isKubeletBootstrapGroup { + return csr, nil + } + + x509cr, err := utilcertificates.ParseCertificateRequestObject(csr) + if err != nil { + glog.Errorf("unable to parse csr %q: %v", csr.ObjectMeta.Name, err) + return csr, nil + } + if !reflect.DeepEqual([]string{"system:nodes"}, x509cr.Subject.Organization) { + return csr, nil + } + if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") { + return csr, nil + } + if len(x509cr.DNSNames)+len(x509cr.EmailAddresses)+len(x509cr.IPAddresses) != 0 { + return csr, nil + } + + csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{ + Type: certificates.CertificateApproved, + Reason: "AutoApproved", + Message: "Auto approving of all kubelet CSRs is enabled on the controller manager", + }) + return cc.kubeClient.Certificates().CertificateSigningRequests().UpdateApproval(csr) +} diff --git a/pkg/controller/certificates/controller_utils.go b/pkg/controller/certificates/controller_utils.go index f2e687c7809..ca3e7f44a22 100644 --- a/pkg/controller/certificates/controller_utils.go +++ b/pkg/controller/certificates/controller_utils.go @@ -21,9 +21,18 @@ import "k8s.io/kubernetes/pkg/apis/certificates" // IsCertificateRequestApproved returns true if a certificate request has the // "Approved" condition and no "Denied" conditions; false otherwise. func IsCertificateRequestApproved(csr *certificates.CertificateSigningRequest) bool { - status := csr.Status - var approved, denied bool - // TODO: incorporate timestamps + approved, denied := getCertApprovalCondition(&csr.Status) + return approved && !denied +} + +// IsCertificateRequestDenied returns true if a certificate request has the +// "Denied" conditions; false otherwise. +func IsCertificateRequestDenied(csr *certificates.CertificateSigningRequest) bool { + _, denied := getCertApprovalCondition(&csr.Status) + return denied +} + +func getCertApprovalCondition(status *certificates.CertificateSigningRequestStatus) (approved bool, denied bool) { for _, c := range status.Conditions { if c.Type == certificates.CertificateApproved { approved = true @@ -32,5 +41,5 @@ func IsCertificateRequestApproved(csr *certificates.CertificateSigningRequest) b denied = true } } - return approved && !denied + return }