Add RSA/ECDSA unit tests for CA issuer
Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
parent
fdfc7f2f77
commit
cf402848b9
@ -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",
|
||||
@ -36,3 +36,22 @@ filegroup(
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"issue_test.go",
|
||||
"util_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/certmanager/v1alpha1:go_default_library",
|
||||
"//pkg/controller/test:go_default_library",
|
||||
"//pkg/issuer:go_default_library",
|
||||
"//pkg/util/pki:go_default_library",
|
||||
"//test/unit/gen: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/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -44,8 +44,10 @@ const (
|
||||
// supporting resources, and to ensure we re-attempt issuance when these resources
|
||||
// are fixed, it always returns an error on any failure.
|
||||
func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.IssueResponse, error) {
|
||||
// get a copy of the existing/currently issued Certificate's private key
|
||||
signeeKey, err := kube.SecretTLSKey(c.secretsLister, crt.Namespace, crt.Spec.SecretName)
|
||||
if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) {
|
||||
// if one does not already exist, generate a new one
|
||||
signeeKey, err = pki.GeneratePrivateKeyForCertificate(crt)
|
||||
if err != nil {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse,
|
||||
@ -59,6 +61,7 @@ func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.Issue
|
||||
return issuer.IssueResponse{}, err
|
||||
}
|
||||
|
||||
// extract the public component of the key
|
||||
publicKey, err := pki.PublicKeyForPrivateKey(signeeKey)
|
||||
if err != nil {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse,
|
||||
@ -66,6 +69,7 @@ func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.Issue
|
||||
return issuer.IssueResponse{}, err
|
||||
}
|
||||
|
||||
// get a copy of the *CA* certificate named on the Issuer
|
||||
caCert, err := kube.SecretTLSCert(c.secretsLister, c.resourceNamespace, c.issuer.GetSpec().CA.SecretName)
|
||||
if err != nil {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse,
|
||||
@ -73,6 +77,7 @@ func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.Issue
|
||||
return issuer.IssueResponse{}, err
|
||||
}
|
||||
|
||||
// actually sign the certificate
|
||||
certPem, err := c.obtainCertificate(crt, publicKey, caCert)
|
||||
if err != nil {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse,
|
||||
@ -88,6 +93,7 @@ func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (issuer.Issue
|
||||
return issuer.IssueResponse{}, err
|
||||
}
|
||||
|
||||
// encode the CA certificate to be bundled in the output
|
||||
caPem, err := pki.EncodeX509(caCert)
|
||||
if err != nil {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse,
|
||||
@ -109,16 +115,19 @@ func (c *CA) obtainCertificate(crt *v1alpha1.Certificate, signeeKey interface{},
|
||||
return nil, fmt.Errorf("no domains specified on certificate")
|
||||
}
|
||||
|
||||
// get a copy of the CAs private key
|
||||
signerKey, err := kube.SecretTLSKey(c.secretsLister, c.resourceNamespace, c.issuer.GetSpec().CA.SecretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting issuer private key: %s", err.Error())
|
||||
}
|
||||
|
||||
// generate a x509 certificate template for this Certificate
|
||||
template, err := pki.GenerateTemplate(c.issuer, crt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sign and encode the certificate
|
||||
crtPem, _, err := pki.SignCertificate(template, signerCert, signeeKey, signerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
227
pkg/issuer/ca/issue_test.go
Normal file
227
pkg/issuer/ca/issue_test.go
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
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 ca
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||
"github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
"github.com/jetstack/cert-manager/test/unit/gen"
|
||||
)
|
||||
|
||||
func generateRSAPrivateKey(t *testing.T) *rsa.PrivateKey {
|
||||
pk, err := pki.GenerateRSAPrivateKey(2048)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate private key: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
func generateECDSAPrivateKey(t *testing.T) *ecdsa.PrivateKey {
|
||||
pk, err := pki.GenerateECPrivateKey(256)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate private key: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
func generateSelfSignedCert(t *testing.T, crt *v1alpha1.Certificate, key crypto.Signer, duration time.Duration) (derBytes, pemBytes []byte) {
|
||||
selfSignedIssuer := gen.Issuer("test", gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}))
|
||||
template, err := pki.GenerateTemplate(selfSignedIssuer, crt)
|
||||
if err != nil {
|
||||
t.Errorf("error generating template: %v", err)
|
||||
}
|
||||
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if err != nil {
|
||||
t.Errorf("error signing cert: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
pemByteBuffer := bytes.NewBuffer([]byte{})
|
||||
err = pem.Encode(pemByteBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
if err != nil {
|
||||
t.Errorf("failed to encode cert: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return derBytes, pemByteBuffer.Bytes()
|
||||
}
|
||||
|
||||
func allFieldsSetCheck(expectedCA []byte) func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
return func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
resp := args[1].(issuer.IssueResponse)
|
||||
|
||||
if resp.PrivateKey == nil {
|
||||
t.Errorf("expected new private key to be generated")
|
||||
}
|
||||
if resp.Certificate == nil {
|
||||
t.Errorf("expected new certificate to be issued")
|
||||
}
|
||||
if resp.CA == nil || !reflect.DeepEqual(expectedCA, resp.CA) {
|
||||
t.Errorf("expected CA certificate to be returned")
|
||||
}
|
||||
if resp.Requeue == true {
|
||||
t.Errorf("expected certificate to not be requeued")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
// Build root RSA CA
|
||||
rsaPK := generateRSAPrivateKey(t)
|
||||
rsaPKBytes := pki.EncodePKCS1PrivateKey(rsaPK)
|
||||
rootRSACrt := gen.Certificate("test-root-ca",
|
||||
gen.SetCertificateCommonName("root-ca"),
|
||||
gen.SetCertificateIsCA(true),
|
||||
)
|
||||
// generate a self signed root ca valid for 60d
|
||||
_, rsaPEMCert := generateSelfSignedCert(t, rootRSACrt, rsaPK, time.Hour*24*60)
|
||||
rootRSACASecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "root-ca-secret",
|
||||
Namespace: gen.DefaultTestNamespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
corev1.TLSPrivateKeyKey: rsaPKBytes,
|
||||
corev1.TLSCertKey: rsaPEMCert,
|
||||
},
|
||||
}
|
||||
|
||||
// Build root ECDSA CA
|
||||
ecdsaPK := generateECDSAPrivateKey(t)
|
||||
ecdsaPKBytes, err := pki.EncodePrivateKey(ecdsaPK)
|
||||
if err != nil {
|
||||
t.Errorf("Error encoding private key: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
rootECDSACrt := gen.Certificate("test-root-ca",
|
||||
gen.SetCertificateCommonName("root-ca"),
|
||||
gen.SetCertificateIsCA(true),
|
||||
)
|
||||
// generate a self signed root ca valid for 60d
|
||||
_, ecdsaPEMCert := generateSelfSignedCert(t, rootECDSACrt, ecdsaPK, time.Hour*24*60)
|
||||
rootECDSACASecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "root-ca-secret",
|
||||
Namespace: gen.DefaultTestNamespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
corev1.TLSPrivateKeyKey: ecdsaPKBytes,
|
||||
corev1.TLSCertKey: ecdsaPEMCert,
|
||||
},
|
||||
}
|
||||
|
||||
tests := map[string]caFixture{
|
||||
"sign a Certificate and generate a new RSA private key": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
Certificate: gen.Certificate("test-crt",
|
||||
gen.SetCertificateSecretName("crt-output"),
|
||||
gen.SetCertificateCommonName("testing-cn"),
|
||||
gen.SetCertificateKeyAlgorithm(v1alpha1.RSAKeyAlgorithm),
|
||||
gen.SetCertificateKeySize(2048),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootRSACASecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: allFieldsSetCheck(rsaPEMCert),
|
||||
Err: false,
|
||||
},
|
||||
"sign a Certificate and generate a new ECDSA private key using RSA issuer": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
Certificate: gen.Certificate("test-crt",
|
||||
gen.SetCertificateSecretName("crt-output"),
|
||||
gen.SetCertificateCommonName("testing-cn"),
|
||||
gen.SetCertificateKeyAlgorithm(v1alpha1.ECDSAKeyAlgorithm),
|
||||
gen.SetCertificateKeySize(521),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootRSACASecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: allFieldsSetCheck(rsaPEMCert),
|
||||
Err: false,
|
||||
},
|
||||
"sign a Certificate and generate a new RSA private key using ECDSA issuer": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
Certificate: gen.Certificate("test-crt",
|
||||
gen.SetCertificateSecretName("crt-output"),
|
||||
gen.SetCertificateCommonName("testing-cn"),
|
||||
gen.SetCertificateKeyAlgorithm(v1alpha1.RSAKeyAlgorithm),
|
||||
gen.SetCertificateKeySize(2048),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootECDSACASecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: allFieldsSetCheck(ecdsaPEMCert),
|
||||
Err: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if test.Builder == nil {
|
||||
test.Builder = &testpkg.Builder{}
|
||||
}
|
||||
test.Setup(t)
|
||||
certCopy := test.Certificate.DeepCopy()
|
||||
resp, err := test.CA.Issue(test.Ctx, certCopy)
|
||||
if err != nil && !test.Err {
|
||||
t.Errorf("Expected function to not error, but got: %v", err)
|
||||
}
|
||||
if err == nil && test.Err {
|
||||
t.Errorf("Expected function to get an error, but got: %v", err)
|
||||
}
|
||||
if resp.Requeue == true {
|
||||
if !reflect.DeepEqual(test.Certificate, certCopy) {
|
||||
t.Errorf("Requeue should never be true if the Certificate is modified to prevent race conditions")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Requeue cannot be true if err is true")
|
||||
}
|
||||
}
|
||||
test.Finish(t, certCopy, resp, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
100
pkg/issuer/ca/util_test.go
Normal file
100
pkg/issuer/ca/util_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
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 ca
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/controller/test"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTestAcmeClusterResourceNamespace = "default"
|
||||
defaultTestSolverImage = "fake-solver-image"
|
||||
)
|
||||
|
||||
type caFixture struct {
|
||||
CA *CA
|
||||
*test.Builder
|
||||
|
||||
Issuer v1alpha1.GenericIssuer
|
||||
Certificate *v1alpha1.Certificate
|
||||
|
||||
PreFn func(*testing.T, *caFixture)
|
||||
CheckFn func(*testing.T, *caFixture, ...interface{})
|
||||
Err bool
|
||||
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
func (s *caFixture) Setup(t *testing.T) {
|
||||
if s.Issuer == nil {
|
||||
s.Issuer = &v1alpha1.Issuer{
|
||||
Spec: v1alpha1.IssuerSpec{
|
||||
IssuerConfig: v1alpha1.IssuerConfig{
|
||||
ACME: &v1alpha1.ACMEIssuer{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if s.Ctx == nil {
|
||||
s.Ctx = context.Background()
|
||||
}
|
||||
if s.Builder == nil {
|
||||
// TODO: set default IssuerOptions
|
||||
// defaultTestAcmeClusterResourceNamespace,
|
||||
// defaultTestSolverImage,
|
||||
// default dns01 nameservers
|
||||
// ambient credentials settings
|
||||
s.Builder = &test.Builder{}
|
||||
}
|
||||
s.CA = s.buildFakeCA(s.Builder, s.Issuer)
|
||||
if s.PreFn != nil {
|
||||
s.PreFn(t, s)
|
||||
s.Builder.Sync()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *caFixture) Finish(t *testing.T, args ...interface{}) {
|
||||
defer s.Builder.Stop()
|
||||
if err := s.Builder.AllReactorsCalled(); err != nil {
|
||||
t.Errorf("Not all expected reactors were called: %v", err)
|
||||
}
|
||||
if err := s.Builder.AllActionsExecuted(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// resync listers before running checks
|
||||
s.Builder.Sync()
|
||||
// run custom checks
|
||||
if s.CheckFn != nil {
|
||||
s.CheckFn(t, s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *caFixture) buildFakeCA(b *test.Builder, issuer v1alpha1.GenericIssuer) *CA {
|
||||
b.Start()
|
||||
a, err := NewCA(b.Context, issuer)
|
||||
if err != nil {
|
||||
panic("error creating fake ca: " + err.Error())
|
||||
}
|
||||
caStruct := a.(*CA)
|
||||
b.Sync()
|
||||
return caStruct
|
||||
}
|
||||
@ -54,6 +54,30 @@ func SetCertificateDNSNames(dnsNames ...string) CertificateModifier {
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateCommonName(commonName string) CertificateModifier {
|
||||
return func(crt *v1alpha1.Certificate) {
|
||||
crt.Spec.CommonName = commonName
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateIsCA(isCA bool) CertificateModifier {
|
||||
return func(crt *v1alpha1.Certificate) {
|
||||
crt.Spec.IsCA = isCA
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateKeyAlgorithm(keyAlgorithm v1alpha1.KeyAlgorithm) CertificateModifier {
|
||||
return func(crt *v1alpha1.Certificate) {
|
||||
crt.Spec.KeyAlgorithm = keyAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateKeySize(keySize int) CertificateModifier {
|
||||
return func(crt *v1alpha1.Certificate) {
|
||||
crt.Spec.KeySize = keySize
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateSecretName(secretName string) CertificateModifier {
|
||||
return func(crt *v1alpha1.Certificate) {
|
||||
crt.Spec.SecretName = secretName
|
||||
|
||||
Loading…
Reference in New Issue
Block a user