/tls/configure and /tls/validate -- make validation failures non-fatal

This commit is contained in:
Eugene Bujak 2019-02-15 17:06:55 +03:00
parent cb97c221fd
commit d44f68e844
2 changed files with 48 additions and 41 deletions

6
app.go
View File

@ -178,9 +178,9 @@ func run(args options) {
} }
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS)) address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS))
// validate current TLS config and update warnings (it could have been loaded from file) // validate current TLS config and update warnings (it could have been loaded from file)
data, err := validateCertificates(config.TLS) data := validateCertificates(config.TLS)
if err != nil { if data.WarningValidation != "" {
log.Fatal(err) log.Fatal(data.WarningValidation)
os.Exit(1) os.Exit(1)
} }
config.TLS = data // update warnings config.TLS = data // update warnings

View File

@ -78,9 +78,7 @@ func writeAllConfigsAndReloadDNS() error {
func httpUpdateConfigReloadDNSReturnOK(w http.ResponseWriter, r *http.Request) { func httpUpdateConfigReloadDNSReturnOK(w http.ResponseWriter, r *http.Request) {
err := writeAllConfigsAndReloadDNS() err := writeAllConfigsAndReloadDNS()
if err != nil { if err != nil {
errorText := fmt.Sprintf("Couldn't write config file: %s", err) httpError(w, http.StatusInternalServerError, "Couldn't write config file: %s", err)
log.Println(errorText)
http.Error(w, errorText, http.StatusInternalServerError)
return return
} }
returnOK(w) returnOK(w)
@ -1039,20 +1037,7 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
// TLS // TLS
// --- // ---
func handleTLSStatus(w http.ResponseWriter, r *http.Request) { func handleTLSStatus(w http.ResponseWriter, r *http.Request) {
data := config.TLS marshalTLS(w, config.TLS)
if data.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain))
data.CertificateChain = string(encoded)
}
if data.PrivateKey != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
data.PrivateKey = string(encoded)
}
err := json.NewEncoder(w).Encode(&data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Failed to marshal json with TLS status: %s", err)
return
}
} }
func handleTLSValidate(w http.ResponseWriter, r *http.Request) { func handleTLSValidate(w http.ResponseWriter, r *http.Request) {
@ -1062,11 +1047,8 @@ func handleTLSValidate(w http.ResponseWriter, r *http.Request) {
return return
} }
data, err = validateCertificates(data) data = validateCertificates(data)
if err != nil { marshalTLS(w, data)
httpError(w, http.StatusBadRequest, "New TLS configuration does not validate: %s", err)
return
}
} }
func handleTLSConfigure(w http.ResponseWriter, r *http.Request) { func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
@ -1076,18 +1058,21 @@ func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
return return
} }
data, err = validateCertificates(data) restartHTTPS := false
data = validateCertificates(data)
if data.WarningValidation == "" {
if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) {
log.Printf("tls config settings have changed, will restart HTTPS server")
restartHTTPS = true
}
config.TLS = data
}
err = writeAllConfigsAndReloadDNS()
if err != nil { if err != nil {
httpError(w, http.StatusBadRequest, "New TLS configuration does not validate: %s", err) httpError(w, http.StatusInternalServerError, "Couldn't write config file: %s", err)
return return
} }
restartHTTPS := false marshalTLS(w, data)
if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) {
log.Printf("tls config settings have changed, will restart HTTPS server")
restartHTTPS = true
}
config.TLS = data
httpUpdateConfigReloadDNSReturnOK(w, r)
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block // this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
if restartHTTPS && httpsServer.server != nil { if restartHTTPS && httpsServer.server != nil {
@ -1098,7 +1083,7 @@ func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
} }
} }
func validateCertificates(data tlsConfig) (tlsConfig, error) { func validateCertificates(data tlsConfig) tlsConfig {
var err error var err error
// clear out status for certificates // clear out status for certificates
@ -1131,13 +1116,15 @@ func validateCertificates(data tlsConfig) (tlsConfig, error) {
for _, cert := range certs { for _, cert := range certs {
parsed, err := x509.ParseCertificate(cert.Bytes) parsed, err := x509.ParseCertificate(cert.Bytes)
if err != nil { if err != nil {
return data, errorx.Decorate(err, "Failed to parse certificate") data.WarningValidation = fmt.Sprintf("Failed to parse certificate: %s", err)
return data
} }
parsedCerts = append(parsedCerts, parsed) parsedCerts = append(parsedCerts, parsed)
} }
if len(parsedCerts) == 0 { if len(parsedCerts) == 0 {
return data, fmt.Errorf("You have specified an empty certificate") data.WarningValidation = fmt.Sprintf("You have specified an empty certificate")
return data
} }
// spew.Dump(parsedCerts) // spew.Dump(parsedCerts)
@ -1223,13 +1210,15 @@ func validateCertificates(data tlsConfig) (tlsConfig, error) {
} }
if key == nil { if key == nil {
return data, fmt.Errorf("No valid keys were found") data.WarningValidation = "No valid keys were found"
return data
} }
// parse the decoded key // parse the decoded key
_, keytype, err := parsePrivateKey(key.Bytes) _, keytype, err := parsePrivateKey(key.Bytes)
if err != nil { if err != nil {
return data, errorx.Decorate(err, "Failed to parse private key") data.WarningValidation = fmt.Sprintf("Failed to parse private key: %s", err)
return data
} }
data.ValidKey = true data.ValidKey = true
@ -1240,11 +1229,12 @@ func validateCertificates(data tlsConfig) (tlsConfig, error) {
if data.PrivateKey != "" && data.CertificateChain != "" { if data.PrivateKey != "" && data.CertificateChain != "" {
_, err = tls.X509KeyPair([]byte(data.CertificateChain), []byte(data.PrivateKey)) _, err = tls.X509KeyPair([]byte(data.CertificateChain), []byte(data.PrivateKey))
if err != nil { if err != nil {
return data, errorx.Decorate(err, "Invalid certificate or key") data.WarningValidation = fmt.Sprintf("Invalid certificate or key: %s", err)
return data
} }
} }
return data, nil return data
} }
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates // Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
@ -1299,6 +1289,23 @@ func unmarshalTLS(r *http.Request) (tlsConfig, error) {
return data, nil return data, nil
} }
func marshalTLS(w http.ResponseWriter, data tlsConfig) {
w.Header().Set("Content-Type", "application/json")
if data.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain))
data.CertificateChain = string(encoded)
}
if data.PrivateKey != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
data.PrivateKey = string(encoded)
}
err := json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Failed to marshal json with TLS status: %s", err)
return
}
}
// ------------------------ // ------------------------
// registration of handlers // registration of handlers
// ------------------------ // ------------------------