Skip to content

Commit e3e7d76

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.
1 parent 1054eb4 commit e3e7d76

File tree

11 files changed

+856
-13
lines changed

11 files changed

+856
-13
lines changed

contrib/completions/bash/openshift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14891,6 +14891,7 @@ _openshift_infra_router()
1489114891
flags+=("--template=")
1489214892
flags+=("--token=")
1489314893
flags+=("--user=")
14894+
flags+=("--validate-route-tls-config")
1489414895
flags+=("--working-dir=")
1489514896
flags+=("--google-json-key=")
1489614897
flags+=("--log-flush-frequency=")

pkg/cmd/infra/router/template.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type TemplateRouter struct {
5454
ReloadInterval time.Duration
5555
DefaultCertificate string
5656
DefaultCertificatePath string
57+
ValidateRouteTLSConfig bool
5758
RouterService *ktypes.NamespacedName
5859
}
5960

@@ -77,6 +78,7 @@ func (o *TemplateRouter) Bind(flag *pflag.FlagSet) {
7778
flag.StringVar(&o.TemplateFile, "template", util.Env("TEMPLATE_FILE", ""), "The path to the template file to use")
7879
flag.StringVar(&o.ReloadScript, "reload", util.Env("RELOAD_SCRIPT", ""), "The path to the reload script to use")
7980
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.")
81+
flag.BoolVar(&o.ValidateRouteTLSConfig, "validate-route-tls-config", util.Env("VALIDATE_ROUTE_TLS_CONFIG", "") == "true", "If set, then an additional validation step is performed on the TLS configuration for all routes admitted in by this router.")
8082
}
8183

8284
type RouterStats struct {
@@ -179,6 +181,7 @@ func (o *TemplateRouterOptions) Run() error {
179181
StatsPassword: o.StatsPassword,
180182
PeerService: o.RouterService,
181183
IncludeUDP: o.RouterSelection.IncludeUDP,
184+
ValidateRouteTLSConfig: o.ValidateRouteTLSConfig,
182185
}
183186

184187
templatePlugin, err := templateplugin.NewTemplatePlugin(pluginCfg)

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: 129 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,64 @@ func ValidateRouteStatusUpdate(route *routeapi.Route, older *routeapi.Route) fie
7679
return allErrs
7780
}
7881

82+
// ValidateRouteTLSConfig checks if the TLS config (certificates and keys) for a route is valid.
83+
func ValidateRouteTLSConfig(route *routeapi.Route) field.ErrorList {
84+
tls := route.Spec.TLS
85+
result := field.ErrorList{}
86+
87+
tlsFieldPath := field.NewPath("spec").Child("tls")
88+
if errs := validateTLS(route, tlsFieldPath); len(errs) != 0 {
89+
result = append(result, errs...)
90+
}
91+
92+
// TODO: Check if we can be stricter with validating the certificate
93+
// is for the route hostname. Don't want existing routes to
94+
// break, so disable the hostname validation for now.
95+
// hostname := route.Spec.Host
96+
hostname := ""
97+
certPool := x509.NewCertPool()
98+
99+
if len(tls.CACertificate) > 0 {
100+
cacert, err := validateCertificatePEM(tls.CACertificate, nil)
101+
if err != nil {
102+
result = append(result, field.Invalid(tlsFieldPath.Child("caCertificate"), tls.CACertificate, err.Error()))
103+
} else {
104+
certPool.AddCert(cacert)
105+
}
106+
}
107+
108+
verifyOptions := &x509.VerifyOptions{
109+
DNSName: hostname,
110+
Roots: certPool,
111+
}
112+
113+
if len(tls.Certificate) > 0 {
114+
if _, err := validateCertificatePEM(tls.Certificate, verifyOptions); err != nil {
115+
result = append(result, field.Invalid(tlsFieldPath.Child("certificate"), tls.Certificate, err.Error()))
116+
}
117+
}
118+
119+
if len(tls.Key) > 0 {
120+
if len(tls.Certificate) > 0 {
121+
if err := validatePrivateKeyForCert(tls.Key, tls.Certificate); err != nil {
122+
result = append(result, field.Invalid(tlsFieldPath.Child("key"), tls.Key, err.Error()))
123+
}
124+
} else {
125+
if err := validatePrivateKeyPEM(tls.Key); err != nil {
126+
result = append(result, field.Invalid(tlsFieldPath.Child("key"), tls.Key, err.Error()))
127+
}
128+
}
129+
}
130+
131+
if len(tls.DestinationCACertificate) > 0 {
132+
if _, err := validateCertificatePEM(tls.DestinationCACertificate, nil); err != nil {
133+
result = append(result, field.Invalid(tlsFieldPath.Child("destinationCACertificate"), tls.DestinationCACertificate, err.Error()))
134+
}
135+
}
136+
137+
return result
138+
}
139+
79140
// validateTLS tests fields for different types of TLS combinations are set. Called
80141
// by ValidateRoute.
81142
func validateTLS(route *routeapi.Route, fldPath *field.Path) field.ErrorList {
@@ -179,3 +240,71 @@ func validateInsecureEdgeTerminationPolicy(tls *routeapi.TLSConfig, fldPath *fie
179240

180241
return nil
181242
}
243+
244+
// validatePrivateKeyForCert checks if a private key PEM is valid and
245+
// is valid for the certificate (if any).
246+
func validatePrivateKeyForCert(keyPEM, certPEM string) error {
247+
if err := validatePrivateKeyPEM(keyPEM); err != nil {
248+
return err
249+
}
250+
251+
if len(certPEM) > 0 {
252+
if _, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)); err != nil {
253+
return err
254+
}
255+
}
256+
257+
return nil
258+
}
259+
260+
// validatePrivateKeyPEM checks if a private key PEM is valid.
261+
func validatePrivateKeyPEM(keyPEM string) error {
262+
if _, err := validatePEMData(keyPEM); err != nil {
263+
return err
264+
}
265+
266+
return nil
267+
}
268+
269+
// validateCertificatePEM checks if a certificate PEM is valid and
270+
// optionally verifies the certificate using the options.
271+
func validateCertificatePEM(certPEM string, options *x509.VerifyOptions) (*x509.Certificate, error) {
272+
data, err := validatePEMData(certPEM)
273+
if err != nil {
274+
return nil, err
275+
}
276+
277+
if data == nil || len(data.Bytes) < 1 {
278+
return nil, fmt.Errorf("invalid/empty certificate data")
279+
}
280+
281+
cert, err := x509.ParseCertificate(data.Bytes)
282+
if err != nil {
283+
return nil, fmt.Errorf("error parsing certificate: %s", err.Error())
284+
}
285+
286+
if options != nil {
287+
_, err = cert.Verify(*options)
288+
if err != nil {
289+
return cert, fmt.Errorf("error verifying certificate: %s", err.Error())
290+
}
291+
}
292+
293+
return cert, nil
294+
}
295+
296+
// validatePEMData checks if the PEM data is valid.
297+
func validatePEMData(pemData string) (*pem.Block, error) {
298+
block, rest := pem.Decode([]byte(pemData))
299+
if block == nil {
300+
return nil, fmt.Errorf("error decoding PEM data")
301+
}
302+
303+
if len(rest) > 0 {
304+
// TODO: Need to verify if failing due to extra data is ok
305+
// or if something needs it. For now, ignore it.
306+
// return block, fmt.Errorf("extra data")
307+
}
308+
309+
return block, nil
310+
}

0 commit comments

Comments
 (0)