|
1 | 1 | package validation
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "crypto/tls" |
| 5 | + "crypto/x509" |
| 6 | + "encoding/pem" |
4 | 7 | "fmt"
|
5 | 8 | "strings"
|
6 | 9 |
|
@@ -76,6 +79,64 @@ func ValidateRouteStatusUpdate(route *routeapi.Route, older *routeapi.Route) fie
|
76 | 79 | return allErrs
|
77 | 80 | }
|
78 | 81 |
|
| 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 | + |
79 | 140 | // validateTLS tests fields for different types of TLS combinations are set. Called
|
80 | 141 | // by ValidateRoute.
|
81 | 142 | func validateTLS(route *routeapi.Route, fldPath *field.Path) field.ErrorList {
|
@@ -179,3 +240,71 @@ func validateInsecureEdgeTerminationPolicy(tls *routeapi.TLSConfig, fldPath *fie
|
179 | 240 |
|
180 | 241 | return nil
|
181 | 242 | }
|
| 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