Merge pull request #761 from kragniz/runtime-validation

Add base of issuer-specific validation to certificates at runtime
This commit is contained in:
jetstack-bot 2018-07-26 11:20:29 +01:00 committed by GitHub
commit 317e6e829c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 238 additions and 3 deletions

View File

@ -0,0 +1,61 @@
package validation
import (
"k8s.io/apimachinery/pkg/util/validation/field"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/issuer"
)
func ValidateCertificateForIssuer(crt *v1alpha1.Certificate, issuerObj v1alpha1.GenericIssuer) field.ErrorList {
el := field.ErrorList{}
path := field.NewPath("spec")
issuerType, err := issuer.NameForIssuer(issuerObj)
if err != nil {
el = append(el, field.Invalid(path, err.Error(), err.Error()))
return el
}
switch issuerType {
case issuer.IssuerACME:
el = append(el, ValidateCertificateForACMEIssuer(&crt.Spec, issuerObj.GetSpec(), path)...)
case issuer.IssuerCA:
el = append(el, ValidateCertificateForCAIssuer(&crt.Spec, issuerObj.GetSpec(), path)...)
case issuer.IssuerVault:
el = append(el, ValidateCertificateForVaultIssuer(&crt.Spec, issuerObj.GetSpec(), path)...)
case issuer.IssuerSelfSigned:
el = append(el, ValidateCertificateForSelfSignedIssuer(&crt.Spec, issuerObj.GetSpec(), path)...)
}
return el
}
func ValidateCertificateForACMEIssuer(crt *v1alpha1.CertificateSpec, issuer *v1alpha1.IssuerSpec, specPath *field.Path) field.ErrorList {
el := field.ErrorList{}
if crt.KeyAlgorithm != v1alpha1.KeyAlgorithm("") && crt.KeyAlgorithm != v1alpha1.RSAKeyAlgorithm {
el = append(el, field.Invalid(specPath.Child("keyAlgorithm"), crt.KeyAlgorithm, "ACME key algorithm must be RSA"))
}
return el
}
func ValidateCertificateForCAIssuer(crt *v1alpha1.CertificateSpec, issuer *v1alpha1.IssuerSpec, specPath *field.Path) field.ErrorList {
el := field.ErrorList{}
return el
}
func ValidateCertificateForVaultIssuer(crt *v1alpha1.CertificateSpec, issuer *v1alpha1.IssuerSpec, specPath *field.Path) field.ErrorList {
el := field.ErrorList{}
return el
}
func ValidateCertificateForSelfSignedIssuer(crt *v1alpha1.CertificateSpec, issuer *v1alpha1.IssuerSpec, specPath *field.Path) field.ErrorList {
el := field.ErrorList{}
return el
}

View File

@ -0,0 +1,158 @@
package validation
import (
"reflect"
"testing"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/test/util/generate"
"k8s.io/apimachinery/pkg/util/validation/field"
)
const (
defaultTestIssuerName = "test-issuer"
defaultTestCrtName = "test-crt"
defaultTestNamespace = "default"
)
func TestValidateCertificateForIssuer(t *testing.T) {
fldPath := field.NewPath("spec")
scenarios := map[string]struct {
crt *v1alpha1.Certificate
issuer *v1alpha1.Issuer
errs []*field.Error
}{
"valid basic certificate": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.ACMECertificateDomainConfig{
{
Domains: []string{"example.com"},
ACMESolverConfig: v1alpha1.ACMESolverConfig{
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
},
},
},
},
},
},
issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
}),
},
"certificate with invalid keyAlgorithm": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
KeyAlgorithm: v1alpha1.KeyAlgorithm("blah"),
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.ACMECertificateDomainConfig{
{
Domains: []string{"example.com"},
ACMESolverConfig: v1alpha1.ACMESolverConfig{
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
},
},
},
},
},
},
issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
}),
errs: []*field.Error{
field.Invalid(fldPath.Child("keyAlgorithm"), v1alpha1.KeyAlgorithm("blah"), "ACME key algorithm must be RSA"),
},
},
"certificate with correct keyAlgorithm for ACME": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
KeyAlgorithm: v1alpha1.RSAKeyAlgorithm,
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.ACMECertificateDomainConfig{
{
Domains: []string{"example.com"},
ACMESolverConfig: v1alpha1.ACMESolverConfig{
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
},
},
},
},
},
},
issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
}),
},
"certificate with incorrect keyAlgorithm for ACME": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
KeyAlgorithm: v1alpha1.ECDSAKeyAlgorithm,
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.ACMECertificateDomainConfig{
{
Domains: []string{"example.com"},
ACMESolverConfig: v1alpha1.ACMESolverConfig{
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
},
},
},
},
},
},
issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
}),
errs: []*field.Error{
field.Invalid(fldPath.Child("keyAlgorithm"), v1alpha1.ECDSAKeyAlgorithm, "ACME key algorithm must be RSA"),
},
},
"certificate with unspecified issuer type": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
KeyAlgorithm: v1alpha1.ECDSAKeyAlgorithm,
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.ACMECertificateDomainConfig{
{
Domains: []string{"example.com"},
ACMESolverConfig: v1alpha1.ACMESolverConfig{
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
},
},
},
},
},
},
issuer: &v1alpha1.Issuer{},
errs: []*field.Error{
field.Invalid(fldPath, "no issuer specified for Issuer '/'", "no issuer specified for Issuer '/'"),
},
},
}
for n, s := range scenarios {
t.Run(n, func(t *testing.T) {
errs := ValidateCertificateForIssuer(s.crt, s.issuer)
if len(errs) != len(s.errs) {
t.Errorf("Expected %v but got %v", s.errs, errs)
return
}
for i, e := range errs {
expectedErr := s.errs[i]
if !reflect.DeepEqual(e, expectedErr) {
t.Errorf("Expected %v but got %v", expectedErr, e)
}
}
})
}
}

View File

@ -82,6 +82,22 @@ func (c *Controller) Sync(ctx context.Context, crt *v1alpha1.Certificate) (err e
return err
}
el = validation.ValidateCertificateForIssuer(crtCopy, issuerObj)
if len(el) > 0 {
msg := fmt.Sprintf("Resource validation failed: %v", el.ToAggregate())
crtCopy.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorConfig, msg, false)
return
} else {
for i, c := range crtCopy.Status.Conditions {
if c.Type == v1alpha1.CertificateConditionReady {
if c.Reason == errorConfig && c.Status == v1alpha1.ConditionFalse {
crtCopy.Status.Conditions = append(crtCopy.Status.Conditions[:i], crtCopy.Status.Conditions[i+1:]...)
break
}
}
}
}
issuerReady := issuerObj.HasCondition(v1alpha1.IssuerCondition{
Type: v1alpha1.IssuerConditionReady,
Status: v1alpha1.ConditionTrue,

View File

@ -17,9 +17,9 @@ const (
IssuerSelfSigned string = "selfsigned"
)
// nameForIssuer determines the name of the issuer implementation given an
// NameForIssuer determines the name of the issuer implementation given an
// Issuer resource.
func nameForIssuer(i v1alpha1.GenericIssuer) (string, error) {
func NameForIssuer(i v1alpha1.GenericIssuer) (string, error) {
switch {
case i.GetSpec().ACME != nil:
return IssuerACME, nil

View File

@ -30,7 +30,7 @@ type factory struct {
// however this is an inexpensive operation and so, Issuers should not need
// to be cached and reused.
func (f *factory) IssuerFor(issuer v1alpha1.GenericIssuer) (Interface, error) {
issuerType, err := nameForIssuer(issuer)
issuerType, err := NameForIssuer(issuer)
if err != nil {
return nil, fmt.Errorf("could not get issuer type: %s", err.Error())
}