e2e tests for Gateway HTTP01 Solver

Signed-off-by: Jake Sanders <i@am.so-aweso.me>
This commit is contained in:
Jake Sanders
2021-08-02 14:12:30 +01:00
parent b38869b551
commit 90e4324c7a
9 changed files with 226 additions and 52 deletions
+2 -2
View File
@@ -37,8 +37,8 @@ data:
@ IN NS localhost.
*.ingress-nginx IN A {SERVICE_IP_PREFIX}.15
ingress-nginx IN A {SERVICE_IP_PREFIX}.15
*.haproxy IN A {SERVICE_IP_PREFIX}.14
haproxy IN A {SERVICE_IP_PREFIX}.14
*.gateway IN A {SERVICE_IP_PREFIX}.14
gateway IN A {SERVICE_IP_PREFIX}.14
db.dns01.example.com: |
;
+2 -20
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Copyright 2020 The cert-manager Authors.
# Copyright 2021 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.
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Install HAProxy as a gateway-API e2e test
# Install HAProxy as a gateway-API e2e test.
set -o nounset
set -o errexit
@@ -71,21 +71,3 @@ spec:
namespaces:
from: All
EOYAML
# Example of a cross namespace HTTPRoute
cat <<EOYAML | kubectl apply -f -
apiVersion: networking.x-k8s.io/v1alpha1
kind: HTTPRoute
metadata:
labels:
acme: solver
name: test
namespace: default
spec:
hostnames:
- blah.haproxy.http01.example.com
rules:
- forwardTo:
- serviceName: echoserver
port: 8080
EOYAML
+1
View File
@@ -7,6 +7,7 @@ go_library(
"addons.go",
"certmanager.go",
"config.go",
"gateway.go",
"ginkgo.go",
"helm.go",
"ingress_controller.go",
+2
View File
@@ -24,6 +24,7 @@ type ACMEServer struct {
URL string
DNSServer string
IngressIP string
GatewayIP string
InvalidACMEURL string
TestingACMEEmail string
TestingACMEEmailAlternative string
@@ -34,6 +35,7 @@ func (p *ACMEServer) AddFlags(fs *flag.FlagSet) {
fs.StringVar(&p.URL, "acme-server-url", "https://pebble.pebble.svc.cluster.local/dir", "URL for the ACME server used during end-to-end tests")
fs.StringVar(&p.DNSServer, "acme-dns-server", "10.0.0.16", "DNS server for ACME DNS01 tests to run against using RFC2136")
fs.StringVar(&p.IngressIP, "acme-ingress-ip", "10.0.0.15", "IP of the ingress server that solves HTTP01 ACME challenges")
fs.StringVar(&p.GatewayIP, "acme-gateway-ip", "10.0.0.14", "IP of the Gateway listener that solves HTTP01 ACME challenges")
fs.StringVar(&p.InvalidACMEURL, "invalid-acme-url", "http://not-a-real-acme-url.com", "An invalid URL to be used during end-to-end tests")
fs.StringVar(&p.TestingACMEEmail, "testing-acme-email", "test@example.com", "Email to be used for the tests")
fs.StringVar(&p.TestingACMEEmailAlternative, "testing-acme-email-alternative", "another-test@example.com", "Alternate email to be used for the tests")
+14 -8
View File
@@ -36,6 +36,10 @@ type Addons struct {
// being used during ACME HTTP01 tests.
IngressController IngressController
// Gateway contains configuration for the Gateway API controller
// being used during HTTP-01 tests.
Gateway Gateway
// Venafi describes global configuration variables for the Venafi tests.
// This includes credentials for the Venafi TPP server to use during runs.
Venafi Venafi
@@ -52,19 +56,21 @@ func (a *Addons) AddFlags(fs *flag.FlagSet) {
a.Helm.AddFlags(fs)
a.ACMEServer.AddFlags(fs)
a.IngressController.AddFlags(fs)
a.Gateway.AddFlags(fs)
a.Venafi.AddFlags(fs)
a.CertManager.AddFlags(fs)
a.DNS01Webhook.AddFlags(fs)
}
func (c *Addons) Validate() []error {
func (a *Addons) Validate() []error {
var errs []error
errs = append(errs, c.Tiller.Validate()...)
errs = append(errs, c.Helm.Validate()...)
errs = append(errs, c.ACMEServer.Validate()...)
errs = append(errs, c.IngressController.Validate()...)
errs = append(errs, c.Venafi.Validate()...)
errs = append(errs, c.CertManager.Validate()...)
errs = append(errs, c.DNS01Webhook.Validate()...)
errs = append(errs, a.Tiller.Validate()...)
errs = append(errs, a.Helm.Validate()...)
errs = append(errs, a.ACMEServer.Validate()...)
errs = append(errs, a.IngressController.Validate()...)
errs = append(errs, a.Gateway.Validate()...)
errs = append(errs, a.Venafi.Validate()...)
errs = append(errs, a.CertManager.Validate()...)
errs = append(errs, a.DNS01Webhook.Validate()...)
return errs
}
+52
View File
@@ -0,0 +1,52 @@
/*
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 config
import "flag"
type Gateway struct {
// Domain is a domain name that is used during e2e tests to solve
// ACME HTTP-01 Challenges.
// It should have suitable records set that resolve *.<domain> to
// the IP of the Gateway's Service.
Domain string
// Labels is a comma separated list of key=value labels set on the
// HTTPRoutes created by the Gateway API solver
Labels string
}
func (g *Gateway) AddFlags(fs *flag.FlagSet) {
fs.StringVar(
&g.Domain,
"gateway-domain",
"gateway.http01.example.com",
"The domain name used during e2e tests to solve HTTP-01 "+
"challenges. This must resolve to the IP of the Gateway's service.",
)
fs.StringVar(
&g.Labels,
"gateway-httproute-labels",
"acme=solver",
"Labels is a comma separated list of key=value labels set on the "+
"HTTPRoutes created by the Gateway API solver",
)
}
func (g *Gateway) Validate() []error {
return nil
}
@@ -58,6 +58,19 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) {
featureset.IssueCAFeature,
)
var unsupportedHTTP01GatewayFeatures = featureset.NewFeatureSet(
featureset.DurationFeature,
featureset.WildcardsFeature,
featureset.URISANsFeature,
featureset.CommonNameFeature,
featureset.KeyUsagesFeature,
featureset.EmailSANsFeature,
featureset.SaveCAToSecret,
featureset.IssueCAFeature,
// Gateway API does not allow raw IP addresses
featureset.IPAddressFeature,
)
// unsupportedDNS01Features is a list of features that are not supported by the ACME
// issuer type using DNS01
var unsupportedDNS01Features = featureset.NewFeatureSet(
@@ -92,13 +105,21 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) {
}
(&certificates.Suite{
Name: "ACME HTTP01 Issuer",
UseIngressIPAddress: true,
CreateIssuerFunc: provisionerHTTP01.createHTTP01Issuer,
Name: "ACME HTTP01 Issuer (Ingress)",
HTTP01TestType: "Ingress",
CreateIssuerFunc: provisionerHTTP01.createHTTP01IngressIssuer,
DeleteIssuerFunc: provisionerHTTP01.delete,
UnsupportedFeatures: unsupportedHTTP01Features,
}).Define()
(&certificates.Suite{
Name: "ACME HTTP01 Issuer (Gateway)",
HTTP01TestType: "Gateway",
CreateIssuerFunc: provisionerHTTP01.createHTTP01GatewayIssuer,
DeleteIssuerFunc: provisionerHTTP01.delete,
UnsupportedFeatures: unsupportedHTTP01GatewayFeatures,
}).Define()
(&certificates.Suite{
Name: "ACME DNS01 Issuer",
DomainSuffix: "dns01.example.com",
@@ -108,13 +129,21 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) {
}).Define()
(&certificates.Suite{
Name: "ACME HTTP01 ClusterIssuer",
UseIngressIPAddress: true,
CreateIssuerFunc: provisionerHTTP01.createHTTP01ClusterIssuer,
Name: "ACME HTTP01 ClusterIssuer (Ingress)",
HTTP01TestType: "Ingress",
CreateIssuerFunc: provisionerHTTP01.createHTTP01IngressClusterIssuer,
DeleteIssuerFunc: provisionerHTTP01.delete,
UnsupportedFeatures: unsupportedHTTP01Features,
}).Define()
(&certificates.Suite{
Name: "ACME HTTP01 ClusterIssuer (Gateway)",
HTTP01TestType: "Gateway",
CreateIssuerFunc: provisionerHTTP01.createHTTP01GatewayClusterIssuer,
DeleteIssuerFunc: provisionerHTTP01.delete,
UnsupportedFeatures: unsupportedHTTP01GatewayFeatures,
}).Define()
(&certificates.Suite{
Name: "ACME DNS01 ClusterIssuer",
DomainSuffix: "dns01.example.com",
@@ -125,7 +154,7 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) {
(&certificates.Suite{
Name: "Public ACME Server HTTP01 Issuer",
UseIngressIPAddress: true,
HTTP01TestType: "Ingress",
CreateIssuerFunc: provisionerPACMEHTTP01.createPublicACMEServerStagingHTTP01Issuer,
DeleteIssuerFunc: provisionerPACMEHTTP01.delete,
UnsupportedFeatures: unsupportedHTTP01Features.Copy().Add(unsupportedPublicACMEServerFeatures.List()...),
@@ -155,15 +184,67 @@ func (a *acmeIssuerProvisioner) delete(f *framework.Framework, ref cmmeta.Object
// - pebble
// - a properly configured Issuer resource
func (a *acmeIssuerProvisioner) createHTTP01Issuer(f *framework.Framework) cmmeta.ObjectReference {
func (a *acmeIssuerProvisioner) createHTTP01IngressIssuer(f *framework.Framework) cmmeta.ObjectReference {
a.ensureEABSecret(f, "")
By("Creating an ACME HTTP01 Issuer")
By("Creating an ACME HTTP01 Ingress Issuer")
issuer := &cmapi.Issuer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "acme-issuer-http01-",
},
Spec: a.createHTTP01IssuerSpec(f.Config.Addons.ACMEServer.URL),
Spec: a.createHTTP01IngressIssuerSpec(f.Config.Addons.ACMEServer.URL),
}
issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 issuer")
return cmmeta.ObjectReference{
Group: cmapi.SchemeGroupVersion.Group,
Kind: cmapi.IssuerKind,
Name: issuer.Name,
}
}
func (a *acmeIssuerProvisioner) createHTTP01IngressClusterIssuer(f *framework.Framework) cmmeta.ObjectReference {
a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace)
By("Creating an ACME HTTP01 Ingress ClusterIssuer")
issuer := &cmapi.ClusterIssuer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "acme-cluster-issuer-http01-",
},
Spec: a.createHTTP01IngressIssuerSpec(f.Config.Addons.ACMEServer.URL),
}
issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 cluster issuer")
return cmmeta.ObjectReference{
Group: cmapi.SchemeGroupVersion.Group,
Kind: cmapi.ClusterIssuerKind,
Name: issuer.Name,
}
}
func (a *acmeIssuerProvisioner) createHTTP01GatewayIssuer(f *framework.Framework) cmmeta.ObjectReference {
a.ensureEABSecret(f, "")
labelFlag := strings.Split(f.Config.Addons.Gateway.Labels, ",")
labels := make(map[string]string)
for _, l := range labelFlag {
kv := strings.Split(l, "=")
if len(kv) != 2 {
continue
}
labels[kv[0]] = kv[1]
}
By("Creating an ACME HTTP01 Gateway Issuer")
issuer := &cmapi.Issuer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "acme-issuer-http01-",
},
Spec: a.createHTTP01GatewayIssuerSpec(f.Config.Addons.ACMEServer.URL, labels),
}
issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{})
@@ -190,7 +271,7 @@ func (a *acmeIssuerProvisioner) createPublicACMEServerStagingHTTP01Issuer(f *fra
ObjectMeta: metav1.ObjectMeta{
GenerateName: "pacme-issuer-http01-",
},
Spec: a.createHTTP01IssuerSpec(PublicACMEServerStagingURL),
Spec: a.createHTTP01IngressIssuerSpec(PublicACMEServerStagingURL),
}
issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{})
@@ -203,15 +284,25 @@ func (a *acmeIssuerProvisioner) createPublicACMEServerStagingHTTP01Issuer(f *fra
}
}
func (a *acmeIssuerProvisioner) createHTTP01ClusterIssuer(f *framework.Framework) cmmeta.ObjectReference {
func (a *acmeIssuerProvisioner) createHTTP01GatewayClusterIssuer(f *framework.Framework) cmmeta.ObjectReference {
a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace)
By("Creating an ACME HTTP01 ClusterIssuer")
labelFlag := strings.Split(f.Config.Addons.Gateway.Labels, ",")
labels := make(map[string]string)
for _, l := range labelFlag {
kv := strings.Split(l, "=")
if len(kv) != 2 {
continue
}
labels[kv[0]] = kv[1]
}
By("Creating an ACME HTTP01 Gateway ClusterIssuer")
issuer := &cmapi.ClusterIssuer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "acme-cluster-issuer-http01-",
},
Spec: a.createHTTP01IssuerSpec(f.Config.Addons.ACMEServer.URL),
Spec: a.createHTTP01GatewayIssuerSpec(f.Config.Addons.ACMEServer.URL, labels),
}
issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{})
@@ -224,7 +315,7 @@ func (a *acmeIssuerProvisioner) createHTTP01ClusterIssuer(f *framework.Framework
}
}
func (a *acmeIssuerProvisioner) createHTTP01IssuerSpec(serverURL string) cmapi.IssuerSpec {
func (a *acmeIssuerProvisioner) createHTTP01IngressIssuerSpec(serverURL string) cmapi.IssuerSpec {
return cmapi.IssuerSpec{
IssuerConfig: cmapi.IssuerConfig{
ACME: &cmacme.ACMEIssuer{
@@ -251,6 +342,32 @@ func (a *acmeIssuerProvisioner) createHTTP01IssuerSpec(serverURL string) cmapi.I
}
}
func (a *acmeIssuerProvisioner) createHTTP01GatewayIssuerSpec(serverURL string, labels map[string]string) cmapi.IssuerSpec {
return cmapi.IssuerSpec{
IssuerConfig: cmapi.IssuerConfig{
ACME: &cmacme.ACMEIssuer{
Server: serverURL,
SkipTLSVerify: true,
PrivateKey: cmmeta.SecretKeySelector{
LocalObjectReference: cmmeta.LocalObjectReference{
Name: "acme-private-key-http01",
},
},
ExternalAccountBinding: a.eab,
Solvers: []cmacme.ACMEChallengeSolver{
{
HTTP01: &cmacme.ACMEChallengeSolverHTTP01{
Gateway: &cmacme.ACMEChallengeSolverHTTP01Gateway{
Labels: labels,
},
},
},
},
},
},
}
}
func (a *acmeIssuerProvisioner) createDNS01Issuer(f *framework.Framework) cmmeta.ObjectReference {
a.ensureEABSecret(f, f.Namespace.Name)
@@ -54,11 +54,9 @@ type Suite struct {
// nginx-ingress addon.
DomainSuffix string
// UseIngressIPAddress indicates that the IPAddress used
// for generating certificates should be the IngressIP.
// The ACME tests need this, so the challenges against the
// IPAddress will complete successfully.
UseIngressIPAddress bool
// HTTP01TestType is set to "Ingress" or "Gateway" to determine which IPs
// and Domains will be used to run the ACME HTTP-01 test suites.
HTTP01TestType string
// UnsupportedFeatures is a list of features that are not supported by this
// invocation of the test suite.
@@ -81,7 +79,12 @@ func (s *Suite) complete(f *framework.Framework) {
}
if s.DomainSuffix == "" {
s.DomainSuffix = f.Config.Addons.IngressController.Domain
switch s.HTTP01TestType {
case "Ingress":
s.DomainSuffix = f.Config.Addons.IngressController.Domain
case "Gateway":
s.DomainSuffix = f.Config.Addons.Gateway.Domain
}
}
if s.UnsupportedFeatures == nil {
@@ -66,8 +66,11 @@ func (s *Suite) Define() {
}
s.complete(f)
if s.UseIngressIPAddress {
switch s.HTTP01TestType {
case "Ingress":
sharedIPAddress = f.Config.Addons.ACMEServer.IngressIP
case "Gateway":
sharedIPAddress = f.Config.Addons.ACMEServer.GatewayIP
}
})
@@ -649,6 +652,10 @@ func (s *Suite) Define() {
}, featureset.ReusePrivateKeyFeature, featureset.OnlySAN)
s.it(f, "should issue a certificate for a single distinct DNS Name defined by an ingress with annotations", func(issuerRef cmmeta.ObjectReference) {
if s.HTTP01TestType != "Ingress" {
Skip("Skipping ingress-specific as non ingress HTTP-01 solver is in use")
return
}
var certName string
switch {
case e2eutil.HasIngresses(f.KubeClientSet.Discovery(), networkingv1.SchemeGroupVersion.String()):
@@ -697,6 +704,10 @@ func (s *Suite) Define() {
}, featureset.OnlySAN)
s.it(f, "should issue a certificate defined by an ingress with certificate field annotations", func(issuerRef cmmeta.ObjectReference) {
if s.HTTP01TestType != "Ingress" {
Skip("Skipping ingress-specific as non ingress HTTP-01 solver is in use")
return
}
var certName string
domain := e2eutil.RandomSubdomain(s.DomainSuffix)
duration := time.Hour * 999