cert-manager/test/e2e/framework/helper/certificaterequests.go
Tim Ramlot 085136068a
fix misspell linter
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
2024-04-29 15:21:07 +02:00

231 lines
8.0 KiB
Go

/*
Copyright 2020 The cert-manager Authors.
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 helper
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"fmt"
"slices"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/cert-manager/cert-manager/e2e-tests/framework/log"
apiutil "github.com/cert-manager/cert-manager/pkg/api/util"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
"github.com/cert-manager/cert-manager/pkg/util"
"github.com/cert-manager/cert-manager/pkg/util/pki"
)
// WaitForCertificateRequestReady waits for the CertificateRequest resource to
// enter a Ready state.
func (h *Helper) WaitForCertificateRequestReady(ns, name string, timeout time.Duration) (*cmapi.CertificateRequest, error) {
var cr *cmapi.CertificateRequest
logf, done := log.LogBackoff()
defer done()
err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) {
var err error
logf("Waiting for CertificateRequest %s to be ready", name)
cr, err = h.CMClient.CertmanagerV1().CertificateRequests(ns).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("error getting CertificateRequest %s: %v", name, err)
}
isReady := apiutil.CertificateRequestHasCondition(cr, cmapi.CertificateRequestCondition{
Type: cmapi.CertificateRequestConditionReady,
Status: cmmeta.ConditionTrue,
})
if !isReady {
logf("Expected CertificateRequest to have Ready condition 'true' but it has: %v", cr.Status.Conditions)
return false, nil
}
return true, nil
})
if err != nil {
return nil, err
}
return cr, nil
}
// ValidateIssuedCertificateRequest will ensure that the given
// CertificateRequest has a certificate issued for it, and that the details on
// the x509 certificate are correct as defined by the CertificateRequest's
// spec.
func (h *Helper) ValidateIssuedCertificateRequest(cr *cmapi.CertificateRequest, key crypto.Signer, rootCAPEM []byte) (*x509.Certificate, error) {
csr, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request)
if err != nil {
return nil, fmt.Errorf("failed to decode CertificateRequest's Spec.Request: %s", err)
}
// validate private key is of the correct type (rsa or ecdsa)
switch csr.PublicKeyAlgorithm {
case x509.RSA:
_, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("Expected private key of type RSA, but it was: %T", key)
}
case x509.ECDSA:
_, ok := key.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key)
}
case x509.Ed25519:
_, ok := key.(ed25519.PrivateKey)
if !ok {
return nil, fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key)
}
default:
return nil, fmt.Errorf("unrecognised requested private key algorithm %q", csr.PublicKeyAlgorithm)
}
// TODO: validate private key KeySize
// check the provided certificate is valid
expectedOrganization := csr.Subject.Organization
expectedDNSNames := csr.DNSNames
expectedIPAddresses := csr.IPAddresses
expectedURIs := csr.URIs
cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate)
if err != nil {
return nil, err
}
commonNameCorrect := true
expectedCN := csr.Subject.CommonName
if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 {
if !slices.Contains(cert.DNSNames, cert.Subject.CommonName) {
commonNameCorrect = false
}
} else if expectedCN != cert.Subject.CommonName {
commonNameCorrect = false
}
if !commonNameCorrect ||
!util.EqualUnsorted(cert.DNSNames, expectedDNSNames) ||
!util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) ||
!util.EqualIPsUnsorted(cert.IPAddresses, expectedIPAddresses) ||
!util.EqualURLsUnsorted(cert.URIs, expectedURIs) {
return nil, fmt.Errorf("Expected certificate valid for CN %q, O %v, dnsNames %v, IPs %v, URIs %v but got a certificate valid for CN %q, O %v, dnsNames %v, IPs %v URIs %v",
expectedCN, expectedOrganization, expectedDNSNames, expectedIPAddresses, expectedURIs,
cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames, cert.IPAddresses, cert.URIs)
}
var expectedDNSName string
if len(expectedDNSNames) > 0 {
expectedDNSName = expectedDNSNames[0]
}
certificateKeyUsages, certificateExtKeyUsages, err := pki.KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA)
if err != nil {
return nil, fmt.Errorf("failed to build key usages from certificate: %s", err)
}
var keyAlg cmapi.PrivateKeyAlgorithm
switch csr.PublicKeyAlgorithm {
case x509.RSA:
keyAlg = cmapi.RSAKeyAlgorithm
case x509.ECDSA:
keyAlg = cmapi.ECDSAKeyAlgorithm
case x509.Ed25519:
keyAlg = cmapi.Ed25519KeyAlgorithm
default:
return nil, fmt.Errorf("unsupported key algorithm type: %s", csr.PublicKeyAlgorithm)
}
defaultCertKeyUsages, defaultCertExtKeyUsages, err := h.defaultKeyUsagesToAdd(cr.Namespace, &cr.Spec.IssuerRef)
if err != nil {
return nil, err
}
certificateKeyUsages |= defaultCertKeyUsages
certificateExtKeyUsages = append(certificateExtKeyUsages, defaultCertExtKeyUsages...)
certificateExtKeyUsages = h.deduplicateExtKeyUsages(certificateExtKeyUsages)
// If using ECDSA then ignore key encipherment
if keyAlg == cmapi.ECDSAKeyAlgorithm {
certificateKeyUsages &^= x509.KeyUsageKeyEncipherment
cert.KeyUsage &^= x509.KeyUsageKeyEncipherment
}
if !h.keyUsagesMatch(cert.KeyUsage, cert.ExtKeyUsage,
certificateKeyUsages, certificateExtKeyUsages) {
return nil, fmt.Errorf("key usages and extended key usages do not match: exp=%s got=%s exp=%s got=%s",
apiutil.KeyUsageStrings(certificateKeyUsages), apiutil.KeyUsageStrings(cert.KeyUsage),
apiutil.ExtKeyUsageStrings(certificateExtKeyUsages), apiutil.ExtKeyUsageStrings(cert.ExtKeyUsage))
}
// TODO: move this verification step out of this function
if rootCAPEM != nil {
rootCertPool := x509.NewCertPool()
rootCertPool.AppendCertsFromPEM(rootCAPEM)
intermediateCertPool := x509.NewCertPool()
intermediateCertPool.AppendCertsFromPEM(cr.Status.CA)
opts := x509.VerifyOptions{
DNSName: expectedDNSName,
Intermediates: intermediateCertPool,
Roots: rootCertPool,
}
if _, err := cert.Verify(opts); err != nil {
return nil, err
}
}
if !apiutil.CertificateRequestIsApproved(cr) {
return nil, fmt.Errorf("CertificateRequest does not have an Approved condition set to True: %+v", cr.Status.Conditions)
}
if apiutil.CertificateRequestIsDenied(cr) {
return nil, fmt.Errorf("CertificateRequest has a Denied condition set to True: %+v", cr.Status.Conditions)
}
return cert, nil
}
func (h *Helper) WaitCertificateRequestIssuedValid(ns, name string, timeout time.Duration, key crypto.Signer) error {
return h.WaitCertificateRequestIssuedValidTLS(ns, name, timeout, key, nil)
}
func (h *Helper) WaitCertificateRequestIssuedValidTLS(ns, name string, timeout time.Duration, key crypto.Signer, rootCAPEM []byte) error {
cr, err := h.WaitForCertificateRequestReady(ns, name, timeout)
if err != nil {
log.Logf("Error waiting for CertificateRequest to become Ready: %v", err)
h.Kubectl(ns).DescribeResource("certificaterequest", name)
h.Kubectl(ns).Describe("order", "challenge")
return err
}
_, err = h.ValidateIssuedCertificateRequest(cr, key, rootCAPEM)
if err != nil {
log.Logf("Error validating issued certificate: %v", err)
h.Kubectl(ns).DescribeResource("certificaterequest", name)
h.Kubectl(ns).Describe("order", "challenge")
return err
}
return nil
}