Skip to content

Commit

Permalink
migrate group approver to use subject access reviews
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedanese committed May 10, 2017
1 parent 1a212ae commit 949d989
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 32 deletions.
2 changes: 1 addition & 1 deletion cmd/kube-controller-manager/app/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet, allControllers []string, disabled
fs.StringVar(&s.ServiceAccountKeyFile, "service-account-private-key-file", s.ServiceAccountKeyFile, "Filename containing a PEM-encoded private RSA or ECDSA 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.EnableKubeletCSRApprover, "enable-kubelet-csr-approver", s.EnableKubeletCSRApprover, "Enable the kubelet CSR approver which approves kubelet CSRs based on subject access reviews")
fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/")
fs.BoolVar(&s.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled")
fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster")
Expand Down
9 changes: 3 additions & 6 deletions pkg/apis/componentconfig/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -828,12 +828,9 @@ type KubeControllerManagerConfiguration struct {
// clusterSigningCertFile is the filename containing a PEM-encoded
// RSA or ECDSA private key used to issue cluster-scoped certificates
ClusterSigningKeyFile string
// 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
// enableKubeletCSRApprover tells the CSR controller to approve
// kubelet CSRs based on SubjectAccessReviews.
EnableKubeletCSRApprover bool
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling bool
// enableContentionProfiling enables lock contention profiling, if enableProfiling is true.
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/certificates/approver/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
srcs = ["groupapprove.go"],
tags = ["automanaged"],
deps = [
"//pkg/apis/authorization/v1beta1:go_default_library",
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/certificates/v1beta1:go_default_library",
Expand Down
101 changes: 76 additions & 25 deletions pkg/controller/certificates/approver/groupapprove.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ import (
"strings"

utilruntime "k8s.io/apimachinery/pkg/util/runtime"
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
capi "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
certificatesinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions/certificates/v1beta1"
"k8s.io/kubernetes/pkg/controller/certificates"
)

func NewCSRApprovingController(
client clientset.Interface,
csrInformer certificatesinformers.CertificateSigningRequestInformer,
approveAllKubeletCSRsForGroup string,
) (*certificates.CertificateController, error) {
const (
selfNodeClientCertAccess = "certificatessigningrequests/selfnodeclientcert"
nodeClientCertAccess = "certificatessigningrequests/nodeclientcert"
nodeServerCertAccess = "certificatessigningrequests/nodeservercert"
)

func NewCSRApprovingController(client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) (*certificates.CertificateController, error) {
approver := &groupApprover{
approveAllKubeletCSRsForGroup: approveAllKubeletCSRsForGroup,
client: client,
}
return certificates.NewCertificateController(
Expand All @@ -46,8 +48,7 @@ func NewCSRApprovingController(

// groupApprover implements AutoApprover for signing Kubelet certificates.
type groupApprover struct {
approveAllKubeletCSRsForGroup string
client clientset.Interface
client clientset.Interface
}

func (ga *groupApprover) handle(csr *capi.CertificateSigningRequest) error {
Expand All @@ -67,17 +68,6 @@ func (ga *groupApprover) handle(csr *capi.CertificateSigningRequest) error {
}

func (cc *groupApprover) autoApprove(csr *capi.CertificateSigningRequest) (*capi.CertificateSigningRequest, error) {
isKubeletBootstrapGroup := false
for _, g := range csr.Spec.Groups {
if g == cc.approveAllKubeletCSRsForGroup {
isKubeletBootstrapGroup = true
break
}
}
if !isKubeletBootstrapGroup {
return csr, nil
}

x509cr, err := capi.ParseCSR(csr)
if err != nil {
utilruntime.HandleError(fmt.Errorf("unable to parse csr %q: %v", csr.Name, err))
Expand All @@ -86,22 +76,77 @@ func (cc *groupApprover) autoApprove(csr *capi.CertificateSigningRequest) (*capi
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
}
if !hasExactUsages(csr, kubeletClientUsages) {
return csr, nil
if hasExactUsages(csr, kubeletClientUsages) {
if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") {
return csr, nil
}
// check kubelet up for client cert renewal
if csr.Spec.Username == x509cr.Subject.CommonName {
approve, err := cc.doAccessReview(csr, selfNodeClientCertAccess)
if err != nil {
return nil, err
}
if approve {
approveWithMessage(csr, "Auto approving of all kubelet client certificate rotation after SAR.")
return csr, nil
}
}
// check kubelet client cret bootstrapper
approve, err := cc.doAccessReview(csr, nodeClientCertAccess)
if err != nil {
return nil, err
}
if approve {
approveWithMessage(csr, "Auto approving of all kubelet client certificate rotation after SAR.")
return csr, nil
}
}
if hasExactUsages(csr, kubeletServerUsages) {
if "system:node:"+x509cr.Subject.CommonName != csr.Spec.Username {
return csr, nil
}
// check kubelet creating server cert
approve, err := cc.doAccessReview(csr, "certificatessigningrequests/nodeservercert")
if err != nil {
return nil, err
}
if approve {
approveWithMessage(csr, "Auto approving of all kubelet client certificate rotation after SAR.")
return csr, nil
}
}
return csr, nil
}

func (cc *groupApprover) doAccessReview(csr *capi.CertificateSigningRequest, verb string) (bool, error) {
extra := make(map[string]authorizationapi.ExtraValue)
for k, v := range csr.Spec.Extra {
extra[k] = authorizationapi.ExtraValue(v)
}

sar := &authorizationapi.SubjectAccessReview{
Spec: authorizationapi.SubjectAccessReviewSpec{
User: csr.Spec.Username,
Groups: csr.Spec.Groups,
Extra: extra,
},
}
sar, err := cc.client.AuthorizationV1beta1().SubjectAccessReviews().Create(sar)
if err != nil {
return false, err
}
return sar.Status.Allowed, nil
}

func approveWithMessage(csr *capi.CertificateSigningRequest, message string) {
csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
Type: capi.CertificateApproved,
Reason: "AutoApproved",
Message: "Auto approving of all kubelet CSRs is enabled on the controller manager",
})
return csr, nil
}

var kubeletClientUsages = []capi.KeyUsage{
Expand All @@ -110,6 +155,12 @@ var kubeletClientUsages = []capi.KeyUsage{
capi.UsageClientAuth,
}

var kubeletServerUsages = []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageServerAuth,
}

func hasExactUsages(csr *capi.CertificateSigningRequest, usages []capi.KeyUsage) bool {
if len(usages) != len(csr.Spec.Usages) {
return false
Expand Down

0 comments on commit 949d989

Please sign in to comment.