Configurable issuer duration and renewBefore Take 2 (#893)

* Configurable issuer duration and renewBefore [1/3]

This is part one of (probably) three parts manually moving the changes from commit 723015174a167d746323f506ab3575cfb243d8bd to the new master. This commit moves the basic functionality of configurable duration while skipping e2e tests and docs. It does not include new work.

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Configurable issuer duration and renewBefore [2/3]

This commit moves over most of the e2e testing updates, some things are intentionally left out as they may be obsolete

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Configurable issuer duration and renewBefore [3/3]

This commit moves the documentation changes, completely the migration of the original code to the latest master

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerunning all hack scripts with since the massive bazel update

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add missing boilerplate headers

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerun codegen hack

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerunning update-docs hack

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix failing unit tests

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix build errors in e2e tests

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerun update-deps

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Don't recreate the CA issuer, it already exists

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Need to create new issuers for the duration and renew time tests because those fields are set in the issuer, so make sure they are named uniquely

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add duration e2e tests for self-signed issuer

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add duration e2e tests for vault w/ custom mount path

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add validation to disallow acme certificates with duration and renewBefore set and update unit tests to verify

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Update docs to mention duration/renew for self-signed issuer and fix potential parsing errors with rst formatting

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Self-signed issuer was missing duration validation

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix a bug causing certificates with a short enough renew-before w.r.t their duration to be renewed instantly and forever

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Print the exact time until renewal

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Move duration and renwal validation to the issuer validation

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Update e2e tests to work with new validation

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add e2e test for the self-signed issuer

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Redo cert duration and renew before to appear as part of the CSR and not the issuer

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Updating tests to match new duration/renewbefore format

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Update e2e tests to match new format

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Update docs to reflect changing the field from issuers to certificates

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove event firing and replace with a TODO as of discussion on PR

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Run hack scripts

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove the sync unit test since without events there is no way to catch the warnings that it was testing

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Use IssuerOptions RenewBeforeExpiryDuration if certificates dont set a renewBefore value for immediate renewal checks

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Delete check on certificate data length in e2e test for certificate duration as there is no reason it should be there

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Update e2e tests since certificate creation will never generate an event

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerunning hack scripts after big rebase

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix a few problems that slipped through during the rebase

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix an e2e error that resulted from the rebase

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add unit test for the calculateTimeBeforeExpiry function

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Adding back in a bunch of missing error checks

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove unused function

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add missing boilerplate

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove unused constant

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Move log constants to function body

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerun hack scripts

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove mistakenly commited file

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove double-import of util package

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix bad function call in e2e vault issuer

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Change duration and renewBefore to be pointer fields as they are optional

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Remove wrong vault issuer test that got passed the rebase somehow

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Change e2e to use pointer format

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Move e2e cert tests out of issuer test file

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Move e2e self-signed issuer test to new location

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Make sure to check for nil in GenerateTemplate

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Add more empty checks to be safe

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Rerunning hacks after rebase

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix bad function call in new e2e test

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Try not setting duration and renewbefore on acme e2e tests

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Zero checks should really just be replaced by nil tests, zero should be caught as any other too-small value

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fixed a missing nil check that got away

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Change e2e duration test format to use pointer times to better simulate API calls

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix sync unit test to match e2e test format

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Fix vault e2e test

Signed-off-by: Max Ehrlich <max.ehr@gmail.com>

* Revert changes to Certificate sync function

Signed-off-by: James Munnelly <james@munnelly.eu>

* Remove selfsigned e2e issuer.go

Signed-off-by: James Munnelly <james@munnelly.eu>

* Don't use ACME issuer in duration example and tidy up line endings

Signed-off-by: James Munnelly <james@munnelly.eu>

* Allow renewBefore to be set on ACME certificates

Signed-off-by: James Munnelly <james@munnelly.eu>

* Update renewBefore ACME docs. Remove unused fields.

Signed-off-by: James Munnelly <james@munnelly.eu>

* Rename calculateTimeBeforeExpiry to calculateDurationUntilRenew

Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
Max Ehrlich 2018-11-14 17:30:00 -05:00 committed by jetstack-bot
parent c3b4bc88ee
commit bbd9249198
35 changed files with 791 additions and 46 deletions

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(function(){navData = {"toc":[{"section":"-strong-field-definitions-strong-","subsections":[{"section":"vaultissuer-v1alpha1"},{"section":"vaultauth-v1alpha1"},{"section":"vaultapprole-v1alpha1"},{"section":"time-v1"},{"section":"statusdetails-v1"},{"section":"statuscause-v1"},{"section":"status-v1"},{"section":"solverconfig-v1alpha1"},{"section":"selfsignedissuer-v1alpha1"},{"section":"secretkeyselector-v1alpha1"},{"section":"ownerreference-v1"},{"section":"objectreference-v1alpha1"},{"section":"objectmeta-v1"},{"section":"listmeta-v1"},{"section":"issuercondition-v1alpha1"},{"section":"initializers-v1"},{"section":"initializer-v1"},{"section":"http01solverconfig-v1alpha1"},{"section":"domainsolverconfig-v1alpha1"},{"section":"dns01solverconfig-v1alpha1"},{"section":"certificatecondition-v1alpha1"},{"section":"caissuer-v1alpha1"},{"section":"acmeissuerhttp01config-v1alpha1"},{"section":"acmeissuerdns01providerroute53-v1alpha1"},{"section":"acmeissuerdns01providerrfc2136-v1alpha1"},{"section":"acmeissuerdns01providerdigitalocean-v1alpha1"},{"section":"acmeissuerdns01providercloudflare-v1alpha1"},{"section":"acmeissuerdns01providerclouddns-v1alpha1"},{"section":"acmeissuerdns01providerazuredns-v1alpha1"},{"section":"acmeissuerdns01providerakamai-v1alpha1"},{"section":"acmeissuerdns01provideracmedns-v1alpha1"},{"section":"acmeissuerdns01provider-v1alpha1"},{"section":"acmeissuerdns01config-v1alpha1"},{"section":"acmeissuer-v1alpha1"},{"section":"acmecertificateconfig-v1alpha1"}]},{"section":"-strong-old-api-versions-strong-","subsections":[]},{"section":"challenge-v1alpha1","subsections":[]},{"section":"order-v1alpha1","subsections":[]},{"section":"issuer-v1alpha1","subsections":[]},{"section":"clusterissuer-v1alpha1","subsections":[]},{"section":"certificate-v1alpha1","subsections":[]},{"section":"-strong-cert-manager-strong-","subsections":[]}],"flatToc":["vaultissuer-v1alpha1","vaultauth-v1alpha1","vaultapprole-v1alpha1","time-v1","statusdetails-v1","statuscause-v1","status-v1","solverconfig-v1alpha1","selfsignedissuer-v1alpha1","secretkeyselector-v1alpha1","ownerreference-v1","objectreference-v1alpha1","objectmeta-v1","listmeta-v1","issuercondition-v1alpha1","initializers-v1","initializer-v1","http01solverconfig-v1alpha1","domainsolverconfig-v1alpha1","dns01solverconfig-v1alpha1","certificatecondition-v1alpha1","caissuer-v1alpha1","acmeissuerhttp01config-v1alpha1","acmeissuerdns01providerroute53-v1alpha1","acmeissuerdns01providerrfc2136-v1alpha1","acmeissuerdns01providerdigitalocean-v1alpha1","acmeissuerdns01providercloudflare-v1alpha1","acmeissuerdns01providerclouddns-v1alpha1","acmeissuerdns01providerazuredns-v1alpha1","acmeissuerdns01providerakamai-v1alpha1","acmeissuerdns01provideracmedns-v1alpha1","acmeissuerdns01provider-v1alpha1","acmeissuerdns01config-v1alpha1","acmeissuer-v1alpha1","acmecertificateconfig-v1alpha1","-strong-field-definitions-strong-","-strong-old-api-versions-strong-","challenge-v1alpha1","order-v1alpha1","issuer-v1alpha1","clusterissuer-v1alpha1","certificate-v1alpha1","-strong-cert-manager-strong-"]};})();
(function(){navData = {"toc":[{"section":"-strong-field-definitions-strong-","subsections":[{"section":"vaultissuer-v1alpha1"},{"section":"vaultauth-v1alpha1"},{"section":"vaultapprole-v1alpha1"},{"section":"time-v1"},{"section":"statusdetails-v1"},{"section":"statuscause-v1"},{"section":"status-v1"},{"section":"solverconfig-v1alpha1"},{"section":"selfsignedissuer-v1alpha1"},{"section":"secretkeyselector-v1alpha1"},{"section":"ownerreference-v1"},{"section":"objectreference-v1alpha1"},{"section":"objectmeta-v1"},{"section":"listmeta-v1"},{"section":"issuercondition-v1alpha1"},{"section":"initializers-v1"},{"section":"initializer-v1"},{"section":"http01solverconfig-v1alpha1"},{"section":"duration-v1"},{"section":"domainsolverconfig-v1alpha1"},{"section":"dns01solverconfig-v1alpha1"},{"section":"certificatecondition-v1alpha1"},{"section":"caissuer-v1alpha1"},{"section":"acmeissuerhttp01config-v1alpha1"},{"section":"acmeissuerdns01providerroute53-v1alpha1"},{"section":"acmeissuerdns01providerrfc2136-v1alpha1"},{"section":"acmeissuerdns01providerdigitalocean-v1alpha1"},{"section":"acmeissuerdns01providercloudflare-v1alpha1"},{"section":"acmeissuerdns01providerclouddns-v1alpha1"},{"section":"acmeissuerdns01providerazuredns-v1alpha1"},{"section":"acmeissuerdns01providerakamai-v1alpha1"},{"section":"acmeissuerdns01provideracmedns-v1alpha1"},{"section":"acmeissuerdns01provider-v1alpha1"},{"section":"acmeissuerdns01config-v1alpha1"},{"section":"acmeissuer-v1alpha1"},{"section":"acmecertificateconfig-v1alpha1"}]},{"section":"-strong-old-api-versions-strong-","subsections":[]},{"section":"challenge-v1alpha1","subsections":[]},{"section":"order-v1alpha1","subsections":[]},{"section":"issuer-v1alpha1","subsections":[]},{"section":"clusterissuer-v1alpha1","subsections":[]},{"section":"certificate-v1alpha1","subsections":[]},{"section":"-strong-cert-manager-strong-","subsections":[]}],"flatToc":["vaultissuer-v1alpha1","vaultauth-v1alpha1","vaultapprole-v1alpha1","time-v1","statusdetails-v1","statuscause-v1","status-v1","solverconfig-v1alpha1","selfsignedissuer-v1alpha1","secretkeyselector-v1alpha1","ownerreference-v1","objectreference-v1alpha1","objectmeta-v1","listmeta-v1","issuercondition-v1alpha1","initializers-v1","initializer-v1","http01solverconfig-v1alpha1","duration-v1","domainsolverconfig-v1alpha1","dns01solverconfig-v1alpha1","certificatecondition-v1alpha1","caissuer-v1alpha1","acmeissuerhttp01config-v1alpha1","acmeissuerdns01providerroute53-v1alpha1","acmeissuerdns01providerrfc2136-v1alpha1","acmeissuerdns01providerdigitalocean-v1alpha1","acmeissuerdns01providercloudflare-v1alpha1","acmeissuerdns01providerclouddns-v1alpha1","acmeissuerdns01providerazuredns-v1alpha1","acmeissuerdns01providerakamai-v1alpha1","acmeissuerdns01provideracmedns-v1alpha1","acmeissuerdns01provider-v1alpha1","acmeissuerdns01config-v1alpha1","acmeissuer-v1alpha1","acmecertificateconfig-v1alpha1","-strong-field-definitions-strong-","-strong-old-api-versions-strong-","challenge-v1alpha1","order-v1alpha1","issuer-v1alpha1","clusterissuer-v1alpha1","certificate-v1alpha1","-strong-cert-manager-strong-"]};})();

View File

@ -35,17 +35,18 @@ A simple Certificate could be defined as:
This Certificate will tell cert-manager to attempt to use the Issuer
named ``letsencrypt-prod`` to obtain a certificate key pair for the
``foo.example.com`` and ``bar.example.com`` domains. If successful, the resulting
key and certificate will be stored in a secret named ``acme-crt-secret`` with
keys of ``tls.key`` and ``tls.crt`` respectively. This secret will live in the
same namespace as the ``Certificate`` resource.
``foo.example.com`` and ``bar.example.com`` domains. If successful, the
resulting key and certificate will be stored in a secret named
``acme-crt-secret`` with keys of ``tls.key`` and ``tls.crt`` respectively.
This secret will live in the same namespace as the ``Certificate`` resource.
The ``dnsNames`` field specifies a list of `Subject Alternative Names`_ to be
associated with the certificate. If the ``commonName`` field is omitted, the
first element in the list will be the common name.
The referenced Issuer must exist in the same namespace as the Certificate. A
Certificate can alternatively reference a ClusterIssuer which is non-namespaced.
The referenced Issuer must exist in the same namespace as the Certificate.
A Certificate can alternatively reference a ClusterIssuer which is
non-namespaced.
.. _`Subject Alternative Names`: https://en.wikipedia.org/wiki/Subject_Alternative_Name
@ -54,3 +55,63 @@ Certificate can alternatively reference a ClusterIssuer which is non-namespaced.
:hidden:
certificates/issuer-specific-config/acme
***************************************
Certificate Duration and Renewal Window
***************************************
cert-manager Certificate resources also support custom validity durations and
renewal windows.
**Important**: The backend service implementation can choose to generate a
certificate with a different validity period than what is requested in the
issuer.
Although the duration and renewal periods are specified on the Certificate
resources, the corresponding Issuer or ClusterIssuer must support this.
The table below shows the support state of the different backend services used
by issuer types:
=========== ============================================================
Issuer Description
=========== ============================================================
ACME Only 'renewBefore' supported
CA Fully supported
Vault Fully supported (although the requested duration must be lower
than the configured Vault role's TTL)
Self Signed Fully supported
=========== ============================================================
The default duration for all certificates is 90 days and the default renewal
windows is 30 days. This means that certificates are considered valid for 3
months and renewal will be attempted within 1 month of expiration.
The *duration* and *renewBefore* parameters must be given in the golang `parseDuration string format <https://golang.org/pkg/time/#ParseDuration>`__.
Example Usage
=============
Here an example of an issuer specifying the duration and renewal window.
The certificate from the previous section is extended with a validity period of
24 hours and to begin trying to renew 12 hours before the certificate
expiration.
.. code-block:: yaml
:linenos:
:emphasize-lines: 7,8
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: example
spec:
secretName: example-tls
duration: 24h
renewBefore: 12h
dnsNames:
- foo.example.com
- bar.example.com
issuerRef:
name: my-internal-ca
kind: Issuer

View File

@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"const.go",
"conversion.go",
"defaults.go",
"doc.go",

View File

@ -0,0 +1,33 @@
/*
Copyright 2018 The Jetstack cert-manager contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import "time"
const (
// minimum permitted certificate duration by cert-manager
MinimumCertificateDuration = time.Hour
// default certificate duration if Issuer.spec.duration is not set
DefaultCertificateDuration = time.Hour * 24 * 90
// minimum certificate duration before certificate expiration
MinimumRenewBefore = time.Minute * 5
// Default duration before certificate expiration if Issuer.spec.renewBefore is not set
DefaultRenewBefore = time.Hour * 24 * 30
)

View File

@ -57,6 +57,12 @@ type CertificateSpec struct {
// Organization is the organization to be used on the Certificate
Organization []string `json:"organization,omitempty"`
// Certificate default Duration
Duration *metav1.Duration `json:"duration,omitempty"`
// Certificate renew before expiration duration
RenewBefore *metav1.Duration `json:"renewBefore,omitempty"`
// DNSNames is a list of subject alt names to be used on the Certificate
DNSNames []string `json:"dnsNames,omitempty"`

View File

@ -468,6 +468,24 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Duration != nil {
in, out := &in.Duration, &out.Duration
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
(*in).DeepCopyInto(*out)
}
}
if in.RenewBefore != nil {
in, out := &in.RenewBefore, &out.RenewBefore
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
(*in).DeepCopyInto(*out)
}
}
if in.DNSNames != nil {
in, out := &in.DNSNames, &out.DNSNames
*out = make([]string, len(*in))

View File

@ -32,6 +32,7 @@ go_test(
"//pkg/issuer/acme/dns/rfc2136:go_default_library",
"//test/util/generate:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)

View File

@ -70,6 +70,10 @@ func ValidateCertificateSpec(crt *v1alpha1.CertificateSpec, fldPath *field.Path)
el = append(el, field.Invalid(fldPath.Child("keyAlgorithm"), crt.KeyAlgorithm, "must be either empty or one of rsa or ecdsa"))
}
if crt.Duration != nil || crt.RenewBefore != nil {
el = append(el, ValidateDuration(crt, fldPath)...)
}
return el
}
@ -148,3 +152,26 @@ func ValidateHTTP01SolverConfig(a *v1alpha1.HTTP01SolverConfig, fldPath *field.P
// TODO: ensure 'ingress' is a valid resource name (i.e. DNS name)
return el
}
func ValidateDuration(crt *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
el := field.ErrorList{}
duration := v1alpha1.DefaultCertificateDuration
if crt.Duration != nil {
duration = crt.Duration.Duration
}
renewBefore := v1alpha1.DefaultRenewBefore
if crt.RenewBefore != nil {
renewBefore = crt.RenewBefore.Duration
}
if duration < v1alpha1.MinimumCertificateDuration {
el = append(el, field.Invalid(fldPath.Child("duration"), duration, fmt.Sprintf("certificate duration must be greater than %s", v1alpha1.MinimumCertificateDuration)))
}
if renewBefore < v1alpha1.MinimumRenewBefore {
el = append(el, field.Invalid(fldPath.Child("renewBefore"), renewBefore, fmt.Sprintf("certificate renewBefore must be greater than %s", v1alpha1.MinimumRenewBefore)))
}
if duration <= renewBefore {
el = append(el, field.Invalid(fldPath.Child("renewBefore"), renewBefore, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", duration, renewBefore)))
}
return el
}

View File

@ -59,6 +59,10 @@ func ValidateCertificateForACMEIssuer(crt *v1alpha1.CertificateSpec, issuer *v1a
el = append(el, field.Invalid(specPath.Child("organization"), crt.Organization, "ACME does not support setting the organization name"))
}
if crt.Duration != nil {
el = append(el, field.Invalid(specPath.Child("duration"), crt.Duration, "ACME does not support certificate durations"))
}
return el
}

View File

@ -19,10 +19,13 @@ package validation
import (
"reflect"
"testing"
"time"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/test/util/generate"
"k8s.io/apimachinery/pkg/util/validation/field"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
@ -130,6 +133,54 @@ func TestValidateCertificateForIssuer(t *testing.T) {
field.Invalid(fldPath.Child("organization"), []string{"shouldfailorg"}, "ACME does not support setting the organization name"),
},
},
"acme certificate with duration set": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: &metav1.Duration{Duration: time.Minute * 60},
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.DomainSolverConfig{
{
Domains: []string{"example.com"},
SolverConfig: v1alpha1.SolverConfig{
HTTP01: &v1alpha1.HTTP01SolverConfig{},
},
},
},
},
},
},
issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
}),
errs: []*field.Error{
field.Invalid(fldPath.Child("duration"), &metav1.Duration{Duration: time.Minute * 60}, "ACME does not support certificate durations"),
},
},
"acme certificate with renewBefore set": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
RenewBefore: &metav1.Duration{Duration: time.Minute * 60},
IssuerRef: validIssuerRef,
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.DomainSolverConfig{
{
Domains: []string{"example.com"},
SolverConfig: v1alpha1.SolverConfig{
HTTP01: &v1alpha1.HTTP01SolverConfig{},
},
},
},
},
},
},
issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
}),
errs: []*field.Error{},
},
"certificate with unspecified issuer type": {
crt: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{

View File

@ -17,11 +17,15 @@ limitations under the License.
package validation
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"k8s.io/apimachinery/pkg/util/validation/field"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
@ -545,3 +549,135 @@ func TestValidateHTTP01SolverConfig(t *testing.T) {
})
}
}
func TestValidateDuration(t *testing.T) {
usefulDurations := map[string]*metav1.Duration{
"one second": {Duration: time.Second},
"ten minutes": {Duration: time.Minute * 10},
"half hour": {Duration: time.Minute * 30},
"one hour": {Duration: time.Hour},
"one month": {Duration: time.Hour * 24 * 30},
"half year": {Duration: time.Hour * 24 * 180},
"one year": {Duration: time.Hour * 24 * 365},
"ten years": {Duration: time.Hour * 24 * 365 * 10},
}
fldPath := field.NewPath("spec")
scenarios := map[string]struct {
cfg *v1alpha1.Certificate
errs []*field.Error
}{
"default duration and renewBefore": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
},
"valid duration and renewBefore": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: usefulDurations["one year"],
RenewBefore: usefulDurations["half year"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
},
"unset duration, valid renewBefore for default": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
RenewBefore: usefulDurations["one month"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
},
"unset renewBefore, valid duration for default": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: usefulDurations["one year"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
},
"renewBefore is bigger than the default duration": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
RenewBefore: usefulDurations["ten years"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
errs: []*field.Error{field.Invalid(fldPath.Child("renewBefore"), usefulDurations["ten years"].Duration, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", v1alpha1.DefaultCertificateDuration, usefulDurations["ten years"].Duration))},
},
"default renewBefore is bigger than the set duration": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: usefulDurations["one hour"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
errs: []*field.Error{field.Invalid(fldPath.Child("renewBefore"), v1alpha1.DefaultRenewBefore, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", usefulDurations["one hour"].Duration, v1alpha1.DefaultRenewBefore))},
},
"renewBefore is bigger than the duration": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: usefulDurations["one month"],
RenewBefore: usefulDurations["one year"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
errs: []*field.Error{field.Invalid(fldPath.Child("renewBefore"), usefulDurations["one year"].Duration, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", usefulDurations["one month"].Duration, usefulDurations["one year"].Duration))},
},
"renewBefore is less than the minimum permitted value": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
RenewBefore: usefulDurations["one second"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
errs: []*field.Error{field.Invalid(fldPath.Child("renewBefore"), usefulDurations["one second"].Duration, fmt.Sprintf("certificate renewBefore must be greater than %s", v1alpha1.MinimumRenewBefore))},
},
"duration is less than the minimum permitted value": {
cfg: &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: usefulDurations["half hour"],
RenewBefore: usefulDurations["ten minutes"],
CommonName: "testcn",
SecretName: "abc",
IssuerRef: validIssuerRef,
},
},
errs: []*field.Error{field.Invalid(fldPath.Child("duration"), usefulDurations["half hour"].Duration, fmt.Sprintf("certificate duration must be greater than %s", v1alpha1.MinimumCertificateDuration))},
},
}
for n, s := range scenarios {
t.Run(n, func(t *testing.T) {
errs := ValidateDuration(&s.cfg.Spec, fldPath)
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

@ -80,6 +80,7 @@ func ValidateIssuerConfig(iss *v1alpha1.IssuerConfig, fldPath *field.Path) field
if numConfigs == 0 {
el = append(el, field.Required(fldPath, "at least one issuer must be configured"))
}
return el
}

View File

@ -18,6 +18,7 @@ go_library(
"//pkg/client/listers/certmanager/v1alpha1:go_default_library",
"//pkg/issuer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",

View File

@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
@ -48,3 +48,13 @@ filegroup(
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["sync_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/certmanager/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)

View File

@ -18,6 +18,7 @@ package certificates
import (
"context"
"crypto/x509"
"fmt"
"reflect"
"strings"
@ -69,6 +70,9 @@ var (
certificateGvk = v1alpha1.SchemeGroupVersion.WithKind("Certificate")
)
// to help testing
var now = time.Now
func (c *Controller) Sync(ctx context.Context, crt *v1alpha1.Certificate) (requeue bool, err error) {
crtCopy := crt.DeepCopy()
defer func() {
@ -180,7 +184,7 @@ func (c *Controller) Sync(ctx context.Context, crt *v1alpha1.Certificate) (reque
}
// check if the certificate needs renewal
needsRenew := c.Context.IssuerOptions.CertificateNeedsRenew(cert)
needsRenew := c.Context.IssuerOptions.CertificateNeedsRenew(cert, crt.Spec.RenewBefore)
if needsRenew {
return c.issue(ctx, i, crtCopy)
}
@ -226,12 +230,11 @@ func (c *Controller) scheduleRenewal(crt *v1alpha1.Certificate) {
return
}
durationUntilExpiry := cert.NotAfter.Sub(time.Now())
renewIn := durationUntilExpiry - c.Context.IssuerOptions.RenewBeforeExpiryDuration
renewIn := c.calculateDurationUntilRenew(cert, crt)
c.scheduledWorkQueue.Add(key, renewIn)
glog.Infof("Certificate %s/%s scheduled for renewal in %d hours", crt.Namespace, crt.Name, renewIn/time.Hour)
glog.Infof("Certificate %s/%s scheduled for renewal in %s", crt.Namespace, crt.Name, renewIn.String())
}
// issuerKind returns the kind of issuer for a certificate
@ -341,3 +344,43 @@ func (c *Controller) updateCertificateStatus(old, new *v1alpha1.Certificate) (*v
// for CRDs (https://github.com/kubernetes/kubernetes/issues/38113)
return c.CMClient.CertmanagerV1alpha1().Certificates(new.Namespace).Update(new)
}
// calculateDurationUntilRenew calculates how long cert-manager should wait to
// until attempting to renew this certificate resource.
func (c *Controller) calculateDurationUntilRenew(cert *x509.Certificate, crt *v1alpha1.Certificate) time.Duration {
messageCertificateDuration := "Certificate received from server has a validity duration of %s. The requested certificate validity duration was %s"
messageScheduleModified := "Certificate renewal duration was changed to fit inside the received certificate validity duration from issuer."
// validate if the certificate received was with the issuer configured
// duration. If not we generate an event to warn the user of that fact.
certDuration := cert.NotAfter.Sub(cert.NotBefore)
if crt.Spec.Duration != nil && certDuration < crt.Spec.Duration.Duration {
s := fmt.Sprintf(messageCertificateDuration, certDuration, crt.Spec.Duration.Duration)
glog.Info(s)
// TODO Use the message as the reason in a 'renewal status' condition
}
// renew is the duration before the certificate expiration that cert-manager
// will start to try renewing the certificate.
renewBefore := v1alpha1.DefaultRenewBefore
if crt.Spec.RenewBefore != nil {
renewBefore = crt.Spec.RenewBefore.Duration
}
// Verify that the renewBefore duration is inside the certificate validity duration.
// If not we notify with an event that we will renew the certificate
// before (certificate duration / 3) of its expiration duration.
if renewBefore > certDuration {
glog.Info(messageScheduleModified)
// TODO Use the message as the reason in a 'renewal status' condition
// We will renew 1/3 before the expiration date.
renewBefore = certDuration / 3
}
// calculate the amount of time until expiry
durationUntilExpiry := cert.NotAfter.Sub(now())
// calculate how long until we should start attempting to renew the certificate
renewIn := durationUntilExpiry - renewBefore
return renewIn
}

View File

@ -0,0 +1,103 @@
/*
Copyright 2018 The Jetstack cert-manager contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificates
import (
"crypto/x509"
"testing"
"time"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestCalculateDurationUntilRenew(t *testing.T) {
c := &Controller{}
currentTime := time.Now()
now = func() time.Time { return currentTime }
defer func() { now = time.Now }()
tests := []struct {
desc string
notBefore time.Time
notAfter time.Time
duration *metav1.Duration
renewBefore *metav1.Duration
expectedExpiry time.Duration
}{
{
desc: "generate an event if certificate duration is lower than requested duration",
notBefore: now(),
notAfter: now().Add(time.Hour * 24 * 90),
duration: &metav1.Duration{time.Hour * 24 * 120},
renewBefore: nil,
expectedExpiry: time.Hour * 24 * 60,
},
{
desc: "default expiry to 30 days",
notBefore: now(),
notAfter: now().Add(time.Hour * 24 * 120),
duration: nil,
renewBefore: nil,
expectedExpiry: (time.Hour * 24 * 120) - (time.Hour * 24 * 30),
},
{
desc: "default expiry to 2/3 of total duration if duration < 30 days",
notBefore: now(),
notAfter: now().Add(time.Hour * 24 * 20),
duration: nil,
renewBefore: nil,
expectedExpiry: time.Hour * 24 * 20 * 2 / 3,
},
{
desc: "expiry of 2/3 of certificate duration when duration < 30 minutes",
notBefore: now(),
notAfter: now().Add(time.Hour),
duration: &metav1.Duration{time.Hour},
renewBefore: &metav1.Duration{time.Hour / 3},
expectedExpiry: time.Hour * 2 / 3,
},
{
desc: "expiry of 60 days of certificate duration",
notBefore: now(),
notAfter: now().Add(time.Hour * 24 * 365),
duration: &metav1.Duration{time.Hour * 24 * 365},
renewBefore: &metav1.Duration{time.Hour * 24 * 60},
expectedExpiry: (time.Hour * 24 * 365) - (time.Hour * 24 * 60),
},
{
desc: "expiry of 2/3 of certificate duration when renewBefore greater than certificate duration",
notBefore: now(),
notAfter: now().Add(time.Hour * 24 * 35),
duration: &metav1.Duration{time.Hour * 24 * 35},
renewBefore: &metav1.Duration{time.Hour * 24 * 40},
expectedExpiry: time.Hour * 24 * 35 * 2 / 3,
},
}
for k, v := range tests {
cert := &v1alpha1.Certificate{
Spec: v1alpha1.CertificateSpec{
Duration: v.duration,
RenewBefore: v.renewBefore,
},
}
x509Cert := &x509.Certificate{NotBefore: v.notBefore, NotAfter: v.notAfter}
duration := c.calculateDurationUntilRenew(x509Cert, cert)
if duration != v.expectedExpiry {
t.Errorf("test # %d - %s: got %v, expected %v", k, v.desc, duration, v.expectedExpiry)
}
}
}

View File

@ -23,6 +23,7 @@ import (
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type Helper interface {
@ -90,12 +91,17 @@ func (o IssuerOptions) CanUseAmbientCredentials(iss cmapi.GenericIssuer) bool {
return false
}
func (o IssuerOptions) CertificateNeedsRenew(cert *x509.Certificate) bool {
func (o IssuerOptions) CertificateNeedsRenew(cert *x509.Certificate, renewBefore *metav1.Duration) bool {
renewBeforeDuration := o.RenewBeforeExpiryDuration
if renewBefore != nil {
renewBeforeDuration = renewBefore.Duration
}
// calculate the amount of time until expiry
durationUntilExpiry := cert.NotAfter.Sub(time.Now())
// calculate how long until we should start attempting to renew the
// certificate
renewIn := durationUntilExpiry - o.RenewBeforeExpiryDuration
renewIn := durationUntilExpiry - renewBeforeDuration
// if we should being attempting to renew now, then trigger a renewal
if renewIn <= 0 {
return true

View File

@ -182,7 +182,7 @@ func (a *Acme) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.Iss
return a.retryOrder(crt, existingOrder)
}
if a.Context.IssuerOptions.CertificateNeedsRenew(x509Cert) {
if a.Context.IssuerOptions.CertificateNeedsRenew(x509Cert, crt.Spec.RenewBefore) {
// existing order's certificate is near expiry
return a.retryOrder(crt, existingOrder)
}

View File

@ -47,8 +47,6 @@ const (
messageErrorIssueCert = "Error issuing TLS certificate: "
messageCertIssued = "Certificate issued successfully"
defaultCertificateDuration = time.Hour * 24 * 90
)
func (v *Vault) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.IssueResponse, error) {
@ -98,7 +96,13 @@ func (v *Vault) obtainCertificate(ctx context.Context, crt *v1alpha1.Certificate
return nil, nil, nil, fmt.Errorf("error encoding certificate request: %s", err.Error())
}
crtBytes, caBytes, err := v.requestVaultCert(template.Subject.CommonName, template.DNSNames, pemRequestBuf.Bytes())
certDuration := v1alpha1.DefaultCertificateDuration
if crt.Spec.Duration != nil {
certDuration = crt.Spec.Duration.Duration
}
crtBytes, caBytes, err := v.requestVaultCert(template.Subject.CommonName, certDuration, template.DNSNames, pemRequestBuf.Bytes())
if err != nil {
return nil, nil, nil, err
}
@ -212,7 +216,7 @@ func (v *Vault) requestTokenWithAppRoleRef(client *vault.Client, appRole *v1alph
return token, nil
}
func (v *Vault) requestVaultCert(commonName string, altNames []string, csr []byte) ([]byte, []byte, error) {
func (v *Vault) requestVaultCert(commonName string, certDuration time.Duration, altNames []string, csr []byte) ([]byte, []byte, error) {
client, err := v.initVaultClient()
if err != nil {
return nil, nil, err
@ -223,7 +227,7 @@ func (v *Vault) requestVaultCert(commonName string, altNames []string, csr []byt
parameters := map[string]string{
"common_name": commonName,
"alt_names": strings.Join(altNames, ","),
"ttl": defaultCertificateDuration.String(),
"ttl": certDuration.String(),
"csr": string(csr),
"exclude_cn_from_sans": "true",
}

View File

@ -86,9 +86,6 @@ func OrganizationForCertificate(crt *v1alpha1.Certificate) []string {
var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)
// default certification duration is 1 year
const defaultNotAfter = time.Hour * 24 * 365
// GenerateCSR will generate a new *x509.CertificateRequest template to be used
// by issuers that utilise CSRs to obtain Certificates.
// The CSR will not be signed, and should be passed to either EncodeCSR or
@ -139,7 +136,13 @@ func GenerateTemplate(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate)
return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
}
certDuration := v1alpha1.DefaultCertificateDuration
if crt.Spec.Duration != nil {
certDuration = crt.Spec.Duration.Duration
}
pubKeyAlgo, _, err := SignatureAlgorithm(crt)
if err != nil {
return nil, err
}
@ -160,7 +163,7 @@ func GenerateTemplate(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate)
CommonName: commonName,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(defaultNotAfter),
NotAfter: time.Now().Add(certDuration),
// see http://golang.org/pkg/crypto/x509/#KeyUsage
KeyUsage: keyUsages,
DNSNames: dnsNames,

View File

@ -240,7 +240,7 @@ func signTestCert(key crypto.Signer) *x509.Certificate {
CommonName: commonName,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(defaultNotAfter),
NotAfter: time.Now().Add(v1alpha1.DefaultCertificateDuration),
// see http://golang.org/pkg/crypto/x509/#KeyUsage
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
}

View File

@ -12,7 +12,9 @@ go_library(
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/certmanager/v1alpha1:go_default_library",
"//pkg/client/clientset/versioned:go_default_library",
"//pkg/util/pki:go_default_library",
"//test/e2e/framework/addon:go_default_library",
"//test/e2e/framework/config:go_default_library",
"//test/e2e/framework/helper:go_default_library",

View File

@ -17,15 +17,23 @@ limitations under the License.
package framework
import (
"time"
"github.com/jetstack/cert-manager/test/e2e/framework/helper"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/util/pki"
"k8s.io/api/core/v1"
api "k8s.io/api/core/v1"
apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
"github.com/jetstack/cert-manager/test/e2e/framework/addon"
"github.com/jetstack/cert-manager/test/e2e/framework/config"
"github.com/jetstack/cert-manager/test/e2e/framework/util"
@ -168,6 +176,22 @@ func (f *Framework) Helper() *helper.Helper {
}
}
func (f *Framework) CertificateDurationValid(c *v1alpha1.Certificate, duration time.Duration) {
By("Verifying TLS certificate exists")
secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(c.Spec.SecretName, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
certBytes, ok := secret.Data[api.TLSCertKey]
if !ok {
Failf("No certificate data found for Certificate %q", c.Name)
}
cert, err := pki.DecodeX509CertificateBytes(certBytes)
Expect(err).NotTo(HaveOccurred())
By("Verifying that the duration is valid")
if cert.NotAfter.Sub(cert.NotBefore) != duration {
Failf("Expected duration of %s, got %s [NotBefore: %s, NotAfter: %s]", duration, cert.NotAfter.Sub(cert.NotBefore), cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
}
}
// CertManagerDescribe is a wrapper function for ginkgo describe. Adds namespacing.
func CertManagerDescribe(text string, body func()) bool {
return Describe("[cert-manager] "+text, body)

View File

@ -116,7 +116,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() {
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
By("Creating a Certificate")
_, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, acmeIngressClass, acmeIngressDomain))
_, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil, acmeIngressClass, acmeIngressDomain))
Expect(err).NotTo(HaveOccurred())
By("Verifying the Certificate is valid")
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
@ -130,7 +130,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() {
// the maximum length of a single segment of the domain being requested
const maxLengthOfDomainSegment = 63
By("Creating a Certificate")
_, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, acmeIngressClass, fmt.Sprintf("%s.%s", cmutil.RandStringRunes(maxLengthOfDomainSegment), acmeIngressDomain)))
_, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil, acmeIngressClass, fmt.Sprintf("%s.%s", cmutil.RandStringRunes(maxLengthOfDomainSegment), acmeIngressDomain)))
Expect(err).NotTo(HaveOccurred())
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
Expect(err).NotTo(HaveOccurred())
@ -141,7 +141,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() {
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
By("Creating a Certificate")
_, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, acmeIngressClass, acmeIngressDomain, fmt.Sprintf("%s.%s", cmutil.RandStringRunes(5), acmeIngressDomain)))
_, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil, acmeIngressClass, acmeIngressDomain, fmt.Sprintf("%s.%s", cmutil.RandStringRunes(5), acmeIngressDomain)))
Expect(err).NotTo(HaveOccurred())
By("Verifying the Certificate is valid")
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
@ -153,7 +153,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() {
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
By("Creating a Certificate")
cert, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, acmeIngressClass, acmeIngressDomain, fmt.Sprintf("%s.%s", cmutil.RandStringRunes(5), acmeIngressDomain)))
cert, err := certClient.Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil, acmeIngressClass, acmeIngressDomain, fmt.Sprintf("%s.%s", cmutil.RandStringRunes(5), acmeIngressDomain)))
Expect(err).NotTo(HaveOccurred())
By("Verifying the Certificate is valid")
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
@ -180,7 +180,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() {
Skip("Poorly designed test skipped until it can be rewritten")
By("Creating a Certificate")
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name).Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, acmeIngressClass, "google.com"))
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name).Create(util.NewCertManagerACMECertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil, acmeIngressClass, "google.com"))
Expect(err).NotTo(HaveOccurred())
By("Waiting for the Certificate to not have a ready condition")
err = util.WaitForCertificateCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name),

View File

@ -18,6 +18,7 @@ go_library(
"//test/util:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)

View File

@ -25,6 +25,7 @@ import (
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/test/e2e/framework"
"github.com/jetstack/cert-manager/test/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = framework.CertManagerDescribe("CA Certificate", func() {
@ -64,7 +65,7 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() {
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
By("Creating a Certificate")
_, err := certClient.Create(util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind))
_, err := certClient.Create(util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil))
Expect(err).NotTo(HaveOccurred())
By("Verifying the Certificate is valid")
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Second*30)
@ -75,7 +76,7 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() {
certClient := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name)
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
crt := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind)
crt := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil)
crt.Spec.KeyAlgorithm = v1alpha1.ECDSAKeyAlgorithm
crt.Spec.KeySize = 521
@ -88,4 +89,39 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() {
Expect(err).NotTo(HaveOccurred())
})
cases := []struct {
inputDuration *metav1.Duration
inputRenewBefore *metav1.Duration
expectedDuration time.Duration
label string
}{
{
inputDuration: &metav1.Duration{time.Hour * 24 * 35},
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 35,
label: "35 days",
},
{
inputDuration: nil,
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 90,
label: "the default duration (90 days)",
},
}
for _, v := range cases {
v := v
It("should generate a signed keypair valid for "+v.label, func() {
certClient := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name)
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
By("Creating a Certificate")
cert, err := certClient.Create(util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, v.inputDuration, v.inputRenewBefore))
Expect(err).NotTo(HaveOccurred())
By("Verifying the Certificate is valid")
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Second*30)
f.CertificateDurationValid(cert, v.expectedDuration)
Expect(err).NotTo(HaveOccurred())
})
}
})

View File

@ -12,6 +12,7 @@ go_library(
"//test/util:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)

View File

@ -17,6 +17,7 @@ limitations under the License.
package certificate
import (
"fmt"
"time"
. "github.com/onsi/ginkgo"
@ -25,6 +26,7 @@ import (
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/test/e2e/framework"
"github.com/jetstack/cert-manager/test/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = framework.CertManagerDescribe("Self Signed Certificate", func() {
@ -51,9 +53,56 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() {
})
Expect(err).NotTo(HaveOccurred())
By("Creating a Certificate")
_, err = certClient.Create(util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind))
_, err = certClient.Create(util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil))
Expect(err).NotTo(HaveOccurred())
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
Expect(err).NotTo(HaveOccurred())
})
cases := []struct {
inputDuration *metav1.Duration
inputRenewBefore *metav1.Duration
expectedDuration time.Duration
label string
}{
{
inputDuration: &metav1.Duration{time.Hour * 24 * 35},
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 35,
label: "35 days",
},
{
inputDuration: nil,
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 90,
label: "the default duration (90 days)",
},
}
for _, v := range cases {
v := v
It("should generate a signed keypair valid for "+v.label, func() {
certClient := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name)
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
By("Creating an Issuer")
issuerDurationName := fmt.Sprintf("%s-%d", issuerName, v.expectedDuration)
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerSelfSignedIssuer(issuerDurationName))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")
err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
issuerDurationName,
v1alpha1.IssuerCondition{
Type: v1alpha1.IssuerConditionReady,
Status: v1alpha1.ConditionTrue,
})
Expect(err).NotTo(HaveOccurred())
By("Creating a Certificate")
cert, err := certClient.Create(util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerDurationName, v1alpha1.IssuerKind, v.inputDuration, v.inputRenewBefore))
Expect(err).NotTo(HaveOccurred())
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Second*30)
f.CertificateDurationValid(cert, v.expectedDuration)
Expect(err).NotTo(HaveOccurred())
})
}
})

View File

@ -17,6 +17,7 @@ go_library(
"//test/util:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)

View File

@ -28,6 +28,7 @@ import (
"github.com/jetstack/cert-manager/test/e2e/framework/addon/tiller"
vaultaddon "github.com/jetstack/cert-manager/test/e2e/framework/addon/vault"
"github.com/jetstack/cert-manager/test/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() {
@ -98,6 +99,7 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() {
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.Details().VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")
@ -110,11 +112,75 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() {
Expect(err).NotTo(HaveOccurred())
By("Creating a Certificate")
_, err = certClient.Create(util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind))
_, err = certClient.Create(util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil))
Expect(err).NotTo(HaveOccurred())
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
Expect(err).NotTo(HaveOccurred())
})
cases := []struct {
inputDuration *metav1.Duration
inputRenewBefore *metav1.Duration
expectedDuration time.Duration
label string
event string
}{
{
inputDuration: &metav1.Duration{time.Hour * 24 * 35},
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 35,
label: "valid for 35 days",
},
{
inputDuration: nil,
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 90,
label: "valid for the default value (90 days)",
},
{
inputDuration: &metav1.Duration{time.Hour * 24 * 365},
inputRenewBefore: nil,
expectedDuration: time.Hour * 24 * 90,
label: "with Vault configured maximum TTL duration (90 days) when requested duration is greater than TTL",
},
{
inputDuration: &metav1.Duration{time.Hour * 24 * 240},
inputRenewBefore: &metav1.Duration{time.Hour * 24 * 120},
expectedDuration: time.Hour * 24 * 90,
label: "with a warning event when renewBefore is bigger than the duration",
},
}
for _, v := range cases {
v := v
It("should generate a new certificate "+v.label, func() {
By("Creating an Issuer")
certClient := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name)
secretClient := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name)
_, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vault.Details().Host, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.Details().VaultCA))
Expect(err).NotTo(HaveOccurred())
By("Waiting for Issuer to become Ready")
err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
issuerName,
v1alpha1.IssuerCondition{
Type: v1alpha1.IssuerConditionReady,
Status: v1alpha1.ConditionTrue,
})
Expect(err).NotTo(HaveOccurred())
By("Creating a Certificate")
cert, err := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name).Create(util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, v.inputDuration, v.inputRenewBefore))
Expect(err).NotTo(HaveOccurred())
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)
// Vault substract 30 seconds to the NotBefore date.
f.CertificateDurationValid(cert, v.expectedDuration+(30*time.Second))
Expect(err).NotTo(HaveOccurred())
})
}
})

View File

@ -111,7 +111,7 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole with a custom
Expect(err).NotTo(HaveOccurred())
By("Creating a Certificate")
_, err = certClient.Create(util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind))
_, err = certClient.Create(util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind, nil, nil))
Expect(err).NotTo(HaveOccurred())
err = util.WaitCertificateIssuedValid(certClient, secretClient, certificateName, time.Minute*5)

View File

@ -94,7 +94,6 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
_, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(vaultaddon.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId))
Expect(err).NotTo(HaveOccurred())
By("Creating an Issuer")
_, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vault.Details().Host, vaultPath, roleId, vaultSecretAppRoleName, authPath, vault.Details().VaultCA))
Expect(err).NotTo(HaveOccurred())

View File

@ -31,6 +31,8 @@ type CertificateConfig struct {
SecretName string
CommonName string
DNSNames []string
Duration *metav1.Duration
RenewBefore *metav1.Duration
// ACME parameters
SolverConfig v1alpha1.SolverConfig
@ -43,7 +45,9 @@ func Certificate(cfg CertificateConfig) *v1alpha1.Certificate {
Namespace: cfg.Namespace,
},
Spec: v1alpha1.CertificateSpec{
SecretName: cfg.SecretName,
Duration: cfg.Duration,
RenewBefore: cfg.RenewBefore,
SecretName: cfg.SecretName,
IssuerRef: v1alpha1.ObjectReference{
Name: cfg.IssuerName,
Kind: cfg.IssuerKind,

View File

@ -350,7 +350,7 @@ func NewCertManagerCAClusterIssuer(name, secretName string) *v1alpha1.ClusterIss
}
}
func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerKind string) *v1alpha1.Certificate {
func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration) *v1alpha1.Certificate {
return &v1alpha1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@ -359,6 +359,8 @@ func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerK
CommonName: "test.domain.com",
Organization: []string{"test-org"},
SecretName: secretName,
Duration: duration,
RenewBefore: renewBefore,
IssuerRef: v1alpha1.ObjectReference{
Name: issuerName,
Kind: issuerKind,
@ -367,15 +369,17 @@ func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerK
}
}
func NewCertManagerACMECertificate(name, secretName, issuerName string, issuerKind string, ingressClass string, cn string, dnsNames ...string) *v1alpha1.Certificate {
func NewCertManagerACMECertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration, ingressClass string, cn string, dnsNames ...string) *v1alpha1.Certificate {
return &v1alpha1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.CertificateSpec{
CommonName: cn,
DNSNames: dnsNames,
SecretName: secretName,
CommonName: cn,
DNSNames: dnsNames,
SecretName: secretName,
Duration: duration,
RenewBefore: renewBefore,
IssuerRef: v1alpha1.ObjectReference{
Name: issuerName,
Kind: issuerKind,
@ -396,14 +400,16 @@ func NewCertManagerACMECertificate(name, secretName, issuerName string, issuerKi
}
}
func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string) *v1alpha1.Certificate {
func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration) *v1alpha1.Certificate {
return &v1alpha1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.CertificateSpec{
CommonName: "test.domain.com",
SecretName: secretName,
CommonName: "test.domain.com",
SecretName: secretName,
Duration: duration,
RenewBefore: renewBefore,
IssuerRef: v1alpha1.ObjectReference{
Name: issuerName,
Kind: issuerKind,