mirror of https://github.com/k3s-io/k3s
Merge pull request #46884 from jcbsmpsn/autoapprover-kubelet-server-certificate
Automatic merge from submit-queue (batch tested with PRs 46884, 47557) Auto approve kubelet server certificate signing requests. Fixes https://github.com/kubernetes/kubernetes/issues/47208 **Release note**: ```release-note Adds an approval work flow to the the certificate approver that will approve certificate signing requests from kubelets that meet all the criteria of kubelet server certificates. ```pull/6/head
commit
aa7458a4ef
|
@ -57,5 +57,6 @@ rules:
|
||||||
- "certificates.k8s.io"
|
- "certificates.k8s.io"
|
||||||
resources:
|
resources:
|
||||||
- certificatesigningrequests/selfnodeclient
|
- certificatesigningrequests/selfnodeclient
|
||||||
|
- certificatesigningrequests/selfnodeserver
|
||||||
verbs:
|
verbs:
|
||||||
- "create"
|
- "create"
|
||||||
|
|
|
@ -33,6 +33,8 @@ go_library(
|
||||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||||
"//pkg/client/informers/informers_generated/externalversions/certificates/v1beta1:go_default_library",
|
"//pkg/client/informers/informers_generated/externalversions/certificates/v1beta1:go_default_library",
|
||||||
"//pkg/controller/certificates:go_default_library",
|
"//pkg/controller/certificates:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,13 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
authorization "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
authorization "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
||||||
capi "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
|
capi "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||||
certificatesinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions/certificates/v1beta1"
|
certificatesinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions/certificates/v1beta1"
|
||||||
"k8s.io/kubernetes/pkg/controller/certificates"
|
"k8s.io/kubernetes/pkg/controller/certificates"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
type csrRecognizer struct {
|
type csrRecognizer struct {
|
||||||
|
@ -54,7 +56,7 @@ func NewCSRApprovingController(client clientset.Interface, csrInformer certifica
|
||||||
}
|
}
|
||||||
|
|
||||||
func recognizers() []csrRecognizer {
|
func recognizers() []csrRecognizer {
|
||||||
return []csrRecognizer{
|
recognizers := []csrRecognizer{
|
||||||
{
|
{
|
||||||
recognize: isSelfNodeClientCert,
|
recognize: isSelfNodeClientCert,
|
||||||
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeclient"},
|
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeclient"},
|
||||||
|
@ -65,12 +67,15 @@ func recognizers() []csrRecognizer {
|
||||||
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "nodeclient"},
|
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "nodeclient"},
|
||||||
successMessage: "Auto approving kubelet client certificate after SubjectAccessReview.",
|
successMessage: "Auto approving kubelet client certificate after SubjectAccessReview.",
|
||||||
},
|
},
|
||||||
{
|
}
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
|
||||||
|
recognizers = append(recognizers, csrRecognizer{
|
||||||
recognize: isSelfNodeServerCert,
|
recognize: isSelfNodeServerCert,
|
||||||
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeserver"},
|
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeserver"},
|
||||||
successMessage: "Auto approving self kubelet server certificate after SubjectAccessReview.",
|
successMessage: "Auto approving self kubelet server certificate after SubjectAccessReview.",
|
||||||
},
|
})
|
||||||
}
|
}
|
||||||
|
return recognizers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *sarApprover) handle(csr *capi.CertificateSigningRequest) error {
|
func (a *sarApprover) handle(csr *capi.CertificateSigningRequest) error {
|
||||||
|
@ -192,9 +197,20 @@ var kubeletServerUsages = []capi.KeyUsage{
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSelfNodeServerCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
|
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) {
|
if !hasExactUsages(csr, kubeletServerUsages) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
//TODO(jcbsmpsn): implement the rest of this
|
if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
if csr.Spec.Username != x509cr.Subject.CommonName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,77 @@ func TestHandle(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelfNodeServerCertRecognizer(t *testing.T) {
|
||||||
|
defaultCSR := csrBuilder{
|
||||||
|
cn: "system:node:foo",
|
||||||
|
orgs: []string{"system:nodes"},
|
||||||
|
requestor: "system:node:foo",
|
||||||
|
usages: []capi.KeyUsage{
|
||||||
|
capi.UsageKeyEncipherment,
|
||||||
|
capi.UsageDigitalSignature,
|
||||||
|
capi.UsageServerAuth,
|
||||||
|
},
|
||||||
|
dns: []string{"node"},
|
||||||
|
ips: []net.IP{net.ParseIP("192.168.0.1")},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
csrBuilder csrBuilder
|
||||||
|
expectedOutcome bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Success - all requirements met",
|
||||||
|
csrBuilder: defaultCSR,
|
||||||
|
expectedOutcome: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No organization",
|
||||||
|
csrBuilder: func(b csrBuilder) csrBuilder {
|
||||||
|
b.orgs = []string{}
|
||||||
|
return b
|
||||||
|
}(defaultCSR),
|
||||||
|
expectedOutcome: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Wrong organization",
|
||||||
|
csrBuilder: func(b csrBuilder) csrBuilder {
|
||||||
|
b.orgs = append(b.orgs, "new-org")
|
||||||
|
return b
|
||||||
|
}(defaultCSR),
|
||||||
|
expectedOutcome: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Wrong usages",
|
||||||
|
csrBuilder: func(b csrBuilder) csrBuilder {
|
||||||
|
b.usages = []capi.KeyUsage{}
|
||||||
|
return b
|
||||||
|
}(defaultCSR),
|
||||||
|
expectedOutcome: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Wrong common name",
|
||||||
|
csrBuilder: func(b csrBuilder) csrBuilder {
|
||||||
|
b.cn = "wrong-common-name"
|
||||||
|
return b
|
||||||
|
}(defaultCSR),
|
||||||
|
expectedOutcome: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
csr := makeFancyTestCsr(tc.csrBuilder)
|
||||||
|
x509cr, err := capi.ParseCSR(csr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
if isSelfNodeServerCert(csr, x509cr) != tc.expectedOutcome {
|
||||||
|
t.Errorf("expected recognized to be %v", tc.expectedOutcome)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRecognizers(t *testing.T) {
|
func TestRecognizers(t *testing.T) {
|
||||||
goodCases := []func(b *csrBuilder){
|
goodCases := []func(b *csrBuilder){
|
||||||
func(b *csrBuilder) {
|
func(b *csrBuilder) {
|
||||||
|
|
|
@ -1134,16 +1134,25 @@ func initializeServerCertificateManager(kubeClient clientset.Interface, kubeCfg
|
||||||
CertificateSigningRequestClient: certSigningRequestClient,
|
CertificateSigningRequestClient: certSigningRequestClient,
|
||||||
Template: &x509.CertificateRequest{
|
Template: &x509.CertificateRequest{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: string(nodeName),
|
CommonName: fmt.Sprintf("system:node:%s", nodeName),
|
||||||
Organization: []string{"system:nodes"},
|
Organization: []string{"system:nodes"},
|
||||||
},
|
},
|
||||||
DNSNames: hostnames,
|
DNSNames: hostnames,
|
||||||
IPAddresses: ips,
|
IPAddresses: ips,
|
||||||
},
|
},
|
||||||
Usages: []certificates.KeyUsage{
|
Usages: []certificates.KeyUsage{
|
||||||
|
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
|
||||||
|
//
|
||||||
|
// Digital signature allows the certificate to be used to verify
|
||||||
|
// digital signatures used during TLS negotiation.
|
||||||
|
certificates.UsageDigitalSignature,
|
||||||
|
// KeyEncipherment allows the cert/key pair to be used to encrypt
|
||||||
|
// keys, including the symetric keys negotiated during TLS setup
|
||||||
|
// and used for data transfer.
|
||||||
certificates.UsageKeyEncipherment,
|
certificates.UsageKeyEncipherment,
|
||||||
|
// ServerAuth allows the cert to be used by a TLS server to
|
||||||
|
// authenticate itself to a TLS client.
|
||||||
certificates.UsageServerAuth,
|
certificates.UsageServerAuth,
|
||||||
certificates.UsageSigning,
|
|
||||||
},
|
},
|
||||||
CertificateStore: certificateStore,
|
CertificateStore: certificateStore,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue