make serviceType configurable, fixes #928
Signed-off-by: Arnold Bechtoldt <arnold.bechtoldt@inovex.de>
This commit is contained in:
parent
d374e33b71
commit
d261e1f3f1
@ -405,7 +405,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1.ACMEIssuerHTTP01Config": {
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Properties: map[string]spec.Schema{},
|
||||
Description: "ACMEIssuerHTTP01Config is a structure containing the ACME HTTP configuration options",
|
||||
Properties: map[string]spec.Schema{
|
||||
"solverServiceType": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{},
|
||||
|
||||
@ -871,6 +871,7 @@ Appears In:
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>ACMEIssuerHTTP01Config is a structure containing the ACME HTTP configuration options</p>
|
||||
<aside class="notice">
|
||||
Appears In:
|
||||
|
||||
@ -887,6 +888,7 @@ Appears In:
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>solverServiceType</code><br /> <em>string</em></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -26,6 +26,15 @@ using Ingress resources
|
||||
http01: {}
|
||||
|
||||
|
||||
To define which Kubernetes service type to use during challenge response specify the following http01 config
|
||||
|
||||
.. code-block:: yaml
|
||||
http01:
|
||||
# Valid values are ClusterIP and NodePort
|
||||
solverServiceType: ClusterIP
|
||||
|
||||
By default type NodePort will be used when you don't set http01 or when you set solverServiceType to an empty string. This may change in future.
|
||||
|
||||
.. note::
|
||||
Let's Encrypt does not support issuing wildcard certificates with HTTP-01 challenges.
|
||||
To issue wildcard certificates, you must use the DNS-01 challenge.
|
||||
|
||||
@ -19,6 +19,7 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/apis/certmanager:go_default_library",
|
||||
"//vendor/github.com/golang/glog: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",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
|
||||
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@ -134,7 +135,9 @@ type ACMEIssuer struct {
|
||||
DNS01 *ACMEIssuerDNS01Config `json:"dns01,omitempty"`
|
||||
}
|
||||
|
||||
// ACMEIssuerHTTP01Config is a structure containing the ACME HTTP configuration options
|
||||
type ACMEIssuerHTTP01Config struct {
|
||||
SolverServiceType corev1.ServiceType `json:"solverServiceType,omitempty"`
|
||||
}
|
||||
|
||||
// ACMEIssuerDNS01Config is a structure containing the ACME DNS configuration
|
||||
|
||||
@ -14,6 +14,7 @@ go_library(
|
||||
"//pkg/apis/certmanager/v1alpha1:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/issuer/acme/dns/rfc2136:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
],
|
||||
)
|
||||
@ -30,6 +31,7 @@ go_test(
|
||||
"//pkg/apis/certmanager/v1alpha1:go_default_library",
|
||||
"//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/util/validation/field:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -17,10 +17,12 @@ limitations under the License.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/rfc2136"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
@ -125,7 +127,26 @@ func ValidateVaultIssuerConfig(iss *v1alpha1.VaultIssuer, fldPath *field.Path) f
|
||||
}
|
||||
|
||||
func ValidateACMEIssuerHTTP01Config(iss *v1alpha1.ACMEIssuerHTTP01Config, fldPath *field.Path) field.ErrorList {
|
||||
return nil
|
||||
el := field.ErrorList{}
|
||||
|
||||
if len(iss.SolverServiceType) > 0 {
|
||||
validTypes := []corev1.ServiceType{
|
||||
corev1.ServiceTypeClusterIP,
|
||||
corev1.ServiceTypeNodePort,
|
||||
}
|
||||
validType := false
|
||||
for _, validTypeName := range validTypes {
|
||||
if iss.SolverServiceType == validTypeName {
|
||||
validType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validType {
|
||||
el = append(el, field.Invalid(fldPath.Child("solverServiceType"), iss.SolverServiceType, fmt.Sprintf("optional field solverServiceType must be one of %q", validTypes)))
|
||||
}
|
||||
}
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
func ValidateACMEIssuerDNS01Config(iss *v1alpha1.ACMEIssuerDNS01Config, fldPath *field.Path) field.ErrorList {
|
||||
|
||||
@ -20,6 +20,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
@ -146,6 +147,19 @@ func TestValidateACMEIssuerConfig(t *testing.T) {
|
||||
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{},
|
||||
},
|
||||
},
|
||||
"acme issue with invalid http01 service config": {
|
||||
spec: &v1alpha1.ACMEIssuer{
|
||||
Email: "valid-email",
|
||||
Server: "valid-server",
|
||||
PrivateKey: validSecretKeyRef,
|
||||
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{
|
||||
SolverServiceType: corev1.ServiceType("InvalidServiceType"),
|
||||
},
|
||||
},
|
||||
errs: []*field.Error{
|
||||
field.Invalid(fldPath.Child("http01", "solverServiceType"), corev1.ServiceType("InvalidServiceType"), "optional field solverServiceType must be one of [\"ClusterIP\" \"NodePort\"]"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for n, s := range scenarios {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
|
||||
@ -81,7 +81,7 @@ func NewSolver(ctx *controller.Context) *Solver {
|
||||
// will return nil (i.e. this function is idempotent).
|
||||
func (s *Solver) Present(ctx context.Context, issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) error {
|
||||
_, podErr := s.ensurePod(crt, ch)
|
||||
svc, svcErr := s.ensureService(crt, ch)
|
||||
svc, svcErr := s.ensureService(issuer, crt, ch)
|
||||
if svcErr != nil {
|
||||
return utilerrors.NewAggregate([]error{podErr, svcErr})
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ import (
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
)
|
||||
|
||||
func (s *Solver) ensureService(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) (*corev1.Service, error) {
|
||||
func (s *Solver) ensureService(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) (*corev1.Service, error) {
|
||||
existingServices, err := s.getServicesForChallenge(crt, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -49,7 +49,7 @@ func (s *Solver) ensureService(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderC
|
||||
}
|
||||
|
||||
glog.Infof("No existing HTTP01 challenge solver service found for Certificate %q. One will be created.", crt.Namespace+"/"+crt.Name)
|
||||
return s.createService(crt, ch)
|
||||
return s.createService(issuer, crt, ch)
|
||||
}
|
||||
|
||||
// getServicesForChallenge returns a list of services that were created to solve
|
||||
@ -85,13 +85,13 @@ func (s *Solver) getServicesForChallenge(crt *v1alpha1.Certificate, ch v1alpha1.
|
||||
|
||||
// createService will create the service required to solve this challenge
|
||||
// in the target API server.
|
||||
func (s *Solver) createService(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) (*corev1.Service, error) {
|
||||
return s.Client.CoreV1().Services(crt.Namespace).Create(buildService(crt, ch))
|
||||
func (s *Solver) createService(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) (*corev1.Service, error) {
|
||||
return s.Client.CoreV1().Services(crt.Namespace).Create(buildService(issuer, crt, ch))
|
||||
}
|
||||
|
||||
func buildService(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) *corev1.Service {
|
||||
func buildService(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) *corev1.Service {
|
||||
podLabels := podLabels(ch)
|
||||
return &corev1.Service{
|
||||
service := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "cm-acme-http-solver-",
|
||||
Namespace: crt.Namespace,
|
||||
@ -102,7 +102,6 @@ func buildService(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) *co
|
||||
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeNodePort,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
@ -113,6 +112,15 @@ func buildService(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) *co
|
||||
Selector: podLabels,
|
||||
},
|
||||
}
|
||||
|
||||
// http01 config is still optional, thus checking for presence and setting previous default
|
||||
if issuer.GetSpec().ACME.HTTP01 == nil {
|
||||
service.Spec.Type = corev1.ServiceTypeNodePort //TODO drop this line in future to use Kubernetes' default
|
||||
} else if issuer.GetSpec().ACME.HTTP01.SolverServiceType != "" {
|
||||
service.Spec.Type = issuer.GetSpec().ACME.HTTP01.SolverServiceType
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func (s *Solver) cleanupServices(crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) error {
|
||||
|
||||
@ -46,7 +46,7 @@ func TestEnsureService(t *testing.T) {
|
||||
Domain: "example.com",
|
||||
},
|
||||
PreFn: func(t *testing.T, s *solverFixture) {
|
||||
svc, err := s.Solver.createService(s.Certificate, s.Challenge)
|
||||
svc, err := s.Solver.createService(s.Issuer, s.Certificate, s.Challenge)
|
||||
if err != nil {
|
||||
t.Errorf("error preparing test: %v", err)
|
||||
}
|
||||
@ -89,7 +89,7 @@ func TestEnsureService(t *testing.T) {
|
||||
Domain: "example.com",
|
||||
},
|
||||
PreFn: func(t *testing.T, s *solverFixture) {
|
||||
expectedService := buildService(s.Certificate, s.Challenge)
|
||||
expectedService := buildService(s.Issuer, s.Certificate, s.Challenge)
|
||||
// create a reactor that fails the test if a service is created
|
||||
s.Builder.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
service := action.(coretesting.CreateAction).GetObject().(*v1.Service)
|
||||
@ -142,11 +142,11 @@ func TestEnsureService(t *testing.T) {
|
||||
},
|
||||
Err: true,
|
||||
PreFn: func(t *testing.T, s *solverFixture) {
|
||||
_, err := s.Solver.createService(s.Certificate, s.Challenge)
|
||||
_, err := s.Solver.createService(s.Issuer, s.Certificate, s.Challenge)
|
||||
if err != nil {
|
||||
t.Errorf("error preparing test: %v", err)
|
||||
}
|
||||
_, err = s.Solver.createService(s.Certificate, s.Challenge)
|
||||
_, err = s.Solver.createService(s.Issuer, s.Certificate, s.Challenge)
|
||||
if err != nil {
|
||||
t.Errorf("error preparing test: %v", err)
|
||||
}
|
||||
@ -169,7 +169,7 @@ func TestEnsureService(t *testing.T) {
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
test.Setup(t)
|
||||
resp, err := test.Solver.ensureService(test.Certificate, test.Challenge)
|
||||
resp, err := test.Solver.ensureService(test.Issuer, test.Certificate, test.Challenge)
|
||||
if err != nil && !test.Err {
|
||||
t.Errorf("Expected function to not error, but got: %v", err)
|
||||
}
|
||||
@ -198,7 +198,7 @@ func TestGetServicesForCertificate(t *testing.T) {
|
||||
Domain: "example.com",
|
||||
},
|
||||
PreFn: func(t *testing.T, s *solverFixture) {
|
||||
ing, err := s.Solver.createService(s.Certificate, s.Challenge)
|
||||
ing, err := s.Solver.createService(s.Issuer, s.Certificate, s.Challenge)
|
||||
if err != nil {
|
||||
t.Errorf("error preparing test: %v", err)
|
||||
}
|
||||
@ -232,7 +232,7 @@ func TestGetServicesForCertificate(t *testing.T) {
|
||||
Domain: "example.com",
|
||||
},
|
||||
PreFn: func(t *testing.T, s *solverFixture) {
|
||||
_, err := s.Solver.createService(s.Certificate, v1alpha1.ACMEOrderChallenge{
|
||||
_, err := s.Solver.createService(s.Issuer, s.Certificate, v1alpha1.ACMEOrderChallenge{
|
||||
Domain: "invaliddomain",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user