Skip to content

Commit 1b2902b

Browse files
committed
o Add basic validation for route TLS configuration - checks that
input is "syntactically" valid. o Checkpoint initial code. o Add support for validating route tls config. o Add option for validating route tls config. o Validation fixes. o Check private key + cert mismatches. o Add tests. o Record route rejection. o Hook into add route processing + store invalid service alias configs in another place - easy to check prior errors on readmission. o Remove entry from invalid service alias configs upon route removal. o Add generated completions. o Bug fixes. o Recording rejecting routes is not working completely. o Fix status update problem - we should set the status to admitted only if we had no errors handling a route. o Rework to use a new controller - extended_validator as per @smarterclayton comments. o Cleanup validation as per @liggitt comments. o Update bash completions. o Fixup older validation unit tests.
1 parent 7ac4cfa commit 1b2902b

File tree

10 files changed

+850
-8
lines changed

10 files changed

+850
-8
lines changed

contrib/completions/bash/openshift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14868,6 +14868,7 @@ _openshift_infra_router()
1486814868
flags+=("--context=")
1486914869
flags+=("--default-certificate=")
1487014870
flags+=("--default-certificate-path=")
14871+
flags+=("--extended-validation")
1487114872
flags+=("--fields=")
1487214873
flags+=("--hostname-template=")
1487314874
flags+=("--include-udp-endpoints")

pkg/cmd/infra/router/template.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/openshift/origin/pkg/cmd/util"
1717
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
18+
"github.com/openshift/origin/pkg/router"
1819
"github.com/openshift/origin/pkg/router/controller"
1920
templateplugin "github.com/openshift/origin/pkg/router/template"
2021
"github.com/openshift/origin/pkg/util/proc"
@@ -54,6 +55,7 @@ type TemplateRouter struct {
5455
ReloadInterval time.Duration
5556
DefaultCertificate string
5657
DefaultCertificatePath string
58+
ExtendedValidation bool
5759
RouterService *ktypes.NamespacedName
5860
}
5961

@@ -77,6 +79,7 @@ func (o *TemplateRouter) Bind(flag *pflag.FlagSet) {
7779
flag.StringVar(&o.TemplateFile, "template", util.Env("TEMPLATE_FILE", ""), "The path to the template file to use")
7880
flag.StringVar(&o.ReloadScript, "reload", util.Env("RELOAD_SCRIPT", ""), "The path to the reload script to use")
7981
flag.DurationVar(&o.ReloadInterval, "interval", reloadInterval(), "Controls how often router reloads are invoked. Mutiple router reload requests are coalesced for the duration of this interval since the last reload time.")
82+
flag.BoolVar(&o.ExtendedValidation, "extended-validation", util.Env("EXTENDED_VALIDATION", "") == "true", "If set, then an additional extended validation step is performed on all routes admitted in by this router.")
8083
}
8184

8285
type RouterStats struct {
@@ -192,7 +195,11 @@ func (o *TemplateRouterOptions) Run() error {
192195
}
193196

194197
statusPlugin := controller.NewStatusAdmitter(templatePlugin, oc, o.RouterName)
195-
plugin := controller.NewUniqueHost(statusPlugin, o.RouteSelectionFunc(), statusPlugin)
198+
var nextPlugin router.Plugin = statusPlugin
199+
if o.ExtendedValidation {
200+
nextPlugin = controller.NewExtendedValidator(nextPlugin, statusPlugin)
201+
}
202+
plugin := controller.NewUniqueHost(nextPlugin, o.RouteSelectionFunc(), statusPlugin)
196203

197204
factory := o.RouterSelection.NewFactory(oc, kc)
198205
controller := factory.Create(plugin)

pkg/route/api/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ type RouteIngressConditionType string
7272
const (
7373
// RouteAdmitted means the route is able to service requests for the provided Host
7474
RouteAdmitted RouteIngressConditionType = "Admitted"
75+
// RouteConfigError means the route configuration is invalid.
76+
RouteConfigError RouteIngressConditionType = "ConfigError"
7577
// TODO: add other route condition types
7678
)
7779

pkg/route/api/validation/validation.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package validation
22

33
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"encoding/pem"
47
"fmt"
58
"strings"
69

@@ -76,6 +79,60 @@ func ValidateRouteStatusUpdate(route *routeapi.Route, older *routeapi.Route) fie
7679
return allErrs
7780
}
7881

82+
// ExtendedValidateRoute performs an extended validation on the route
83+
// including checking that the TLS config is valid.
84+
func ExtendedValidateRoute(route *routeapi.Route) field.ErrorList {
85+
tlsConfig := route.Spec.TLS
86+
result := field.ErrorList{}
87+
88+
tlsFieldPath := field.NewPath("spec").Child("tls")
89+
if errs := validateTLS(route, tlsFieldPath); len(errs) != 0 {
90+
result = append(result, errs...)
91+
}
92+
93+
// TODO: Check if we can be stricter with validating the certificate
94+
// is for the route hostname. Don't want existing routes to
95+
// break, so disable the hostname validation for now.
96+
// hostname := route.Spec.Host
97+
hostname := ""
98+
var certPool *x509.CertPool
99+
100+
if len(tlsConfig.CACertificate) > 0 {
101+
certPool = x509.NewCertPool()
102+
if ok := certPool.AppendCertsFromPEM([]byte(tlsConfig.CACertificate)); !ok {
103+
result = append(result, field.Invalid(tlsFieldPath.Child("caCertificate"), tlsConfig.CACertificate, "failed to parse CA certificate"))
104+
}
105+
}
106+
107+
verifyOptions := &x509.VerifyOptions{
108+
DNSName: hostname,
109+
Roots: certPool,
110+
}
111+
112+
if len(tlsConfig.Certificate) > 0 {
113+
if _, err := validateCertificatePEM(tlsConfig.Certificate, verifyOptions); err != nil {
114+
result = append(result, field.Invalid(tlsFieldPath.Child("certificate"), tlsConfig.Certificate, err.Error()))
115+
}
116+
}
117+
118+
if len(tlsConfig.Key) > 0 {
119+
if len(tlsConfig.Certificate) > 0 {
120+
if _, err := tls.X509KeyPair([]byte(tlsConfig.Certificate), []byte(tlsConfig.Key)); err != nil {
121+
result = append(result, field.Invalid(tlsFieldPath.Child("key"), tlsConfig.Key, err.Error()))
122+
}
123+
}
124+
}
125+
126+
if len(tlsConfig.DestinationCACertificate) > 0 {
127+
roots := x509.NewCertPool()
128+
if ok := roots.AppendCertsFromPEM([]byte(tlsConfig.DestinationCACertificate)); !ok {
129+
result = append(result, field.Invalid(tlsFieldPath.Child("destinationCACertificate"), tlsConfig.DestinationCACertificate, "failed to parse destination CA certificate"))
130+
}
131+
}
132+
133+
return result
134+
}
135+
79136
// validateTLS tests fields for different types of TLS combinations are set. Called
80137
// by ValidateRoute.
81138
func validateTLS(route *routeapi.Route, fldPath *field.Path) field.ErrorList {
@@ -179,3 +236,37 @@ func validateInsecureEdgeTerminationPolicy(tls *routeapi.TLSConfig, fldPath *fie
179236

180237
return nil
181238
}
239+
240+
// validateCertificatePEM checks if a certificate PEM is valid and
241+
// optionally verifies the certificate using the options.
242+
func validateCertificatePEM(certPEM string, options *x509.VerifyOptions) (*x509.Certificate, error) {
243+
var data *pem.Block
244+
for remaining := []byte(certPEM); len(remaining) > 0; {
245+
block, rest := pem.Decode(remaining)
246+
if block == nil {
247+
return nil, fmt.Errorf("error decoding certificate data")
248+
}
249+
if block.Type == "CERTIFICATE" {
250+
data = block
251+
}
252+
remaining = rest
253+
}
254+
255+
if data == nil || len(data.Bytes) < 1 {
256+
return nil, fmt.Errorf("invalid/empty certificate data")
257+
}
258+
259+
cert, err := x509.ParseCertificate(data.Bytes)
260+
if err != nil {
261+
return nil, fmt.Errorf("error parsing certificate: %s", err.Error())
262+
}
263+
264+
if options != nil {
265+
_, err = cert.Verify(*options)
266+
if err != nil {
267+
return cert, fmt.Errorf("error verifying certificate: %s", err.Error())
268+
}
269+
}
270+
271+
return cert, nil
272+
}

0 commit comments

Comments
 (0)