Record Failed condition in signer controller

This commit is contained in:
Jordan Liggitt
2020-04-16 00:51:33 -04:00
parent 377adfa2b7
commit 57eddd5e04
5 changed files with 129 additions and 40 deletions

View File

@@ -186,7 +186,7 @@ func (cc *CertificateController) syncFunc(key string) error {
return err
}
if csr.Status.Certificate != nil {
if len(csr.Status.Certificate) > 0 {
// no need to do anything because it already has a cert
return nil
}

View File

@@ -16,7 +16,10 @@ limitations under the License.
package certificates
import certificates "k8s.io/api/certificates/v1beta1"
import (
certificates "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
)
// IsCertificateRequestApproved returns true if a certificate request has the
// "Approved" condition and no "Denied" conditions; false otherwise.
@@ -25,6 +28,16 @@ func IsCertificateRequestApproved(csr *certificates.CertificateSigningRequest) b
return approved && !denied
}
// HasCondition returns true if the csr contains a condition of the specified type with a status that is set to True or is empty
func HasTrueCondition(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) bool {
for _, c := range csr.Status.Conditions {
if c.Type == conditionType && (len(c.Status) == 0 || c.Status == v1.ConditionTrue) {
return true
}
}
return false
}
func GetCertApprovalCondition(status *certificates.CertificateSigningRequestStatus) (approved bool, denied bool) {
for _, c := range status.Conditions {
if c.Type == certificates.CertificateApproved {

View File

@@ -25,6 +25,7 @@ import (
"time"
capi "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
@@ -106,7 +107,7 @@ func (c *CSRSigningController) Run(workers int, stopCh <-chan struct{}) {
c.certificateController.Run(workers, stopCh)
}
type isRequestForSignerFunc func(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool
type isRequestForSignerFunc func(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error)
type signer struct {
caProvider *caProvider
@@ -139,8 +140,8 @@ func newSigner(signerName, caFile, caKeyFile string, client clientset.Interface,
}
func (s *signer) handle(csr *capi.CertificateSigningRequest) error {
// Ignore unapproved requests
if !certificates.IsCertificateRequestApproved(csr) {
// Ignore unapproved or failed requests
if !certificates.IsCertificateRequestApproved(csr) || certificates.HasTrueCondition(csr, capi.CertificateFailed) {
return nil
}
@@ -153,9 +154,21 @@ func (s *signer) handle(csr *capi.CertificateSigningRequest) error {
if err != nil {
return fmt.Errorf("unable to parse csr %q: %v", csr.Name, err)
}
if !s.isRequestForSignerFn(x509cr, csr.Spec.Usages, *csr.Spec.SignerName) {
// TODO: mark the CertificateRequest as being in a terminal state and
// communicate to the user why the request has been refused.
if recognized, err := s.isRequestForSignerFn(x509cr, csr.Spec.Usages, *csr.Spec.SignerName); err != nil {
csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
Type: capi.CertificateFailed,
Status: v1.ConditionTrue,
Reason: "SignerValidationFailure",
Message: err.Error(),
LastUpdateTime: metav1.Now(),
})
_, err = s.client.CertificatesV1beta1().CertificateSigningRequests().UpdateStatus(context.TODO(), csr, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("error adding failure condition for csr: %v", err)
}
return nil
} else if !recognized {
// Ignore requests for kubernetes.io signerNames we don't recognize
return nil
}
cert, err := s.sign(x509cr, csr.Spec.Usages)
@@ -201,41 +214,41 @@ func getCSRVerificationFuncForSignerName(signerName string) (isRequestForSignerF
// TODO type this error so that a different reporting loop (one without a signing cert), can mark
// CSRs with unknown kube signers as terminal if we wish. This largely depends on how tightly we want to control
// our signerNames.
return nil, fmt.Errorf("unrecongized signerName: %q", signerName)
return nil, fmt.Errorf("unrecognized signerName: %q", signerName)
}
}
func isKubeletServing(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isKubeletServing(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.KubeletServingSignerName {
return false
return false, nil
}
return capihelper.IsKubeletServingCSR(req, usages)
return true, capihelper.ValidateKubeletServingCSR(req, usages)
}
func isKubeletClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isKubeletClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.KubeAPIServerClientKubeletSignerName {
return false
return false, nil
}
return capihelper.IsKubeletClientCSR(req, usages)
return true, capihelper.ValidateKubeletClientCSR(req, usages)
}
func isKubeAPIServerClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isKubeAPIServerClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.KubeAPIServerClientSignerName {
return false
return false, nil
}
return validAPIServerClientUsages(usages)
return true, validAPIServerClientUsages(usages)
}
func isLegacyUnknown(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isLegacyUnknown(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.LegacyUnknownSignerName {
return false
return false, nil
}
// No restrictions are applied to the legacy-unknown signerName to
// maintain backward compatibility in v1beta1.
return true
return true, nil
}
func validAPIServerClientUsages(usages []capi.KeyUsage) bool {
func validAPIServerClientUsages(usages []capi.KeyUsage) error {
hasClientAuth := false
for _, u := range usages {
switch u {
@@ -244,8 +257,11 @@ func validAPIServerClientUsages(usages []capi.KeyUsage) bool {
case capi.UsageClientAuth:
hasClientAuth = true
default:
return false
return fmt.Errorf("invalid usage for client certificate: %s", u)
}
}
return hasClientAuth
if !hasClientAuth {
return fmt.Errorf("missing required usage for client certificate: %s", capi.UsageClientAuth)
}
return nil
}

View File

@@ -35,6 +35,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
testclient "k8s.io/client-go/testing"
"k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/controller/certificates"
capihelper "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
)
@@ -114,6 +115,8 @@ func TestHandle(t *testing.T) {
usages []capi.KeyUsage
// whether the generated CSR should be marked as approved
approved bool
// whether the generated CSR should be marked as failed
failed bool
// the signerName to be set on the generated CSR
signerName string
// if true, expect an error to be returned
@@ -149,10 +152,17 @@ func TestHandle(t *testing.T) {
usages: []capi.KeyUsage{capi.UsageServerAuth, capi.UsageClientAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
approved: true,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 0 {
t.Errorf("expected no Update action but got %d", len(as))
if len(as) != 1 {
t.Errorf("expected one Update action but got %d", len(as))
return
}
csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
if len(csr.Status.Certificate) != 0 {
t.Errorf("expected no certificate to be issued")
}
if !certificates.HasTrueCondition(csr, capi.CertificateFailed) {
t.Errorf("expected Failed condition")
}
},
},
{
@@ -207,6 +217,21 @@ func TestHandle(t *testing.T) {
}
},
},
{
name: "should do nothing if failed",
signerName: "kubernetes.io/kubelet-serving",
commonName: "system:node:testnode",
org: []string{"system:nodes"},
usages: []capi.KeyUsage{capi.UsageServerAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
dnsNames: []string{"example.com"},
approved: true,
failed: true,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 0 {
t.Errorf("expected no action to be taken")
}
},
},
{
name: "should do nothing if an unrecognised signerName is used",
signerName: "kubernetes.io/not-recognised",
@@ -267,7 +292,7 @@ func TestHandle(t *testing.T) {
// continue with rest of test
}
csr := makeTestCSR(csrBuilder{cn: c.commonName, signerName: c.signerName, approved: c.approved, usages: c.usages, org: c.org, dnsNames: c.dnsNames})
csr := makeTestCSR(csrBuilder{cn: c.commonName, signerName: c.signerName, approved: c.approved, failed: c.failed, usages: c.usages, org: c.org, dnsNames: c.dnsNames})
if err := s.handle(csr); err != nil && !c.err {
t.Errorf("unexpected err: %v", err)
}
@@ -286,6 +311,7 @@ type csrBuilder struct {
org []string
signerName string
approved bool
failed bool
usages []capi.KeyUsage
}
@@ -318,5 +344,10 @@ func makeTestCSR(b csrBuilder) *capi.CertificateSigningRequest {
Type: capi.CertificateApproved,
})
}
if b.failed {
csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
Type: capi.CertificateFailed,
})
}
return csr
}