From 24bb708b21e158712e4c67ceca2816defd44ca88 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 13 Aug 2019 12:32:52 +0300 Subject: [PATCH] + config: add certificate_path, private_key_path * POST /control/tls/configure: support certificate_path and private_key_path --- AGHTechDoc.md | 63 +++++++++++++++++++++++++++++++++++ dnsforward/dnsforward.go | 10 ++++-- dnsforward/dnsforward_test.go | 6 ++-- home/config.go | 6 ++++ home/control_tls.go | 57 +++++++++++++++++++++++++++++-- home/home.go | 14 ++++---- 6 files changed, 142 insertions(+), 14 deletions(-) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 54becca5..b4b7bd61 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -12,6 +12,9 @@ Contents: * Updating * Get version command * Update command +* TLS + * API: Get TLS configuration + * API: Set TLS configuration * Device Names and Per-client Settings * Per-client settings * Get list of clients @@ -515,6 +518,66 @@ Response: 200 OK +## TLS + + +### API: Get TLS configuration + +Request: + + GET /control/tls/status + +Response: + + 200 OK + + { + "enabled":true, + "server_name":"...", + "port_https":443, + "port_dns_over_tls":853, + "certificate_chain":"...", + "private_key":"...", + "certificate_path":"...", + "private_key_path":"..." + + "subject":"CN=...", + "issuer":"CN=...", + "not_before":"2019-03-19T08:23:45Z", + "not_after":"2029-03-16T08:23:45Z", + "dns_names":null, + "key_type":"RSA", + "valid_cert":true, + "valid_key":true, + "valid_chain":false, + "valid_pair":true, + "warning_validation":"Your certificate does not verify: x509: certificate signed by unknown authority" + } + + +### API: Set TLS configuration + +Request: + + POST /control/tls/configure + + { + "enabled":true, + "server_name":"hostname", + "force_https":false, + "port_https":443, + "port_dns_over_tls":853, + "certificate_chain":"...", + "private_key":"...", + "certificate_path":"...", // if set, certificate_chain must be empty + "private_key_path":"..." // if set, private_key must be empty + } + +Response: + + 200 OK + + ## Device Names and Per-client Settings When a client requests information from DNS server, he's identified by IP address. diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index 43fda4c4..845c8ca6 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -108,6 +108,12 @@ type TLSConfig struct { TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"` CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` // PEM-encoded certificates chain PrivateKey string `yaml:"private_key" json:"private_key"` // PEM-encoded private key + + CertificatePath string `yaml:"certificate_path" json:"certificate_path"` // certificate file name + PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"` // private key file name + + CertificateChainData []byte `yaml:"-" json:"-"` + PrivateKeyData []byte `yaml:"-" json:"-"` } // ServerConfig represents server configuration. @@ -216,9 +222,9 @@ func (s *Server) startInternal(config *ServerConfig) error { convertArrayToMap(&s.BlockedHosts, s.conf.BlockedHosts) - if s.conf.TLSListenAddr != nil && s.conf.CertificateChain != "" && s.conf.PrivateKey != "" { + if s.conf.TLSListenAddr != nil && len(s.conf.CertificateChainData) != 0 && len(s.conf.PrivateKeyData) != 0 { proxyConfig.TLSListenAddr = s.conf.TLSListenAddr - keypair, err := tls.X509KeyPair([]byte(s.conf.CertificateChain), []byte(s.conf.PrivateKey)) + keypair, err := tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData) if err != nil { return errorx.Decorate(err, "Failed to parse TLS keypair") } diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go index ffcb4d67..cd16f714 100644 --- a/dnsforward/dnsforward_test.go +++ b/dnsforward/dnsforward_test.go @@ -118,9 +118,9 @@ func TestDotServer(t *testing.T) { defer removeDataDir(t) s.conf.TLSConfig = TLSConfig{ - TLSListenAddr: &net.TCPAddr{Port: 0}, - CertificateChain: string(certPem), - PrivateKey: string(keyPem), + TLSListenAddr: &net.TCPAddr{Port: 0}, + CertificateChainData: certPem, + PrivateKeyData: keyPem, } // Starting the server diff --git a/home/config.go b/home/config.go index fd3460ea..1c558e16 100644 --- a/home/config.go +++ b/home/config.go @@ -279,6 +279,12 @@ func parseConfig() error { } config.Clients = nil + status := tlsConfigStatus{} + if !tlsLoadConfig(&config.TLS, &status) { + log.Error("%s", status.WarningValidation) + return err + } + // Deduplicate filters deduplicateFilters() diff --git a/home/control_tls.go b/home/control_tls.go index f1f3d98d..312cf30d 100644 --- a/home/control_tls.go +++ b/home/control_tls.go @@ -14,6 +14,7 @@ import ( "encoding/pem" "errors" "fmt" + "io/ioutil" "net/http" "reflect" "strings" @@ -23,6 +24,41 @@ import ( "github.com/joomcode/errorx" ) +// Set certificate and private key data +func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool { + tls.CertificateChainData = []byte(tls.CertificateChain) + tls.PrivateKeyData = []byte(tls.PrivateKey) + + var err error + if tls.CertificatePath != "" { + if tls.CertificateChain != "" { + status.WarningValidation = "certificate data and file can't be set together" + return false + } + tls.CertificateChainData, err = ioutil.ReadFile(tls.CertificatePath) + if err != nil { + status.WarningValidation = err.Error() + return false + } + status.ValidCert = true + } + + if tls.PrivateKeyPath != "" { + if tls.PrivateKey != "" { + status.WarningValidation = "private key data and file can't be set together" + return false + } + tls.PrivateKeyData, err = ioutil.ReadFile(tls.PrivateKeyPath) + if err != nil { + status.WarningValidation = err.Error() + return false + } + status.ValidKey = true + } + + return true +} + // RegisterTLSHandlers registers HTTP handlers for TLS configuration func RegisterTLSHandlers() { httpRegister(http.MethodGet, "/control/tls/status", handleTLSStatus) @@ -55,7 +91,12 @@ func handleTLSValidate(w http.ResponseWriter, r *http.Request) { } } - data.tlsConfigStatus = validateCertificates(data.CertificateChain, data.PrivateKey, data.ServerName) + status := tlsConfigStatus{} + if tlsLoadConfig(&data, &status) { + status = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName) + } + data.tlsConfigStatus = status + marshalTLS(w, data) } @@ -80,8 +121,14 @@ func handleTLSConfigure(w http.ResponseWriter, r *http.Request) { } } + status := tlsConfigStatus{} + if !tlsLoadConfig(&data, &status) { + data.tlsConfigStatus = status + marshalTLS(w, data) + return + } + data.tlsConfigStatus = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName) restartHTTPS := false - data.tlsConfigStatus = validateCertificates(data.CertificateChain, data.PrivateKey, data.ServerName) if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) { log.Printf("tls config settings have changed, will restart HTTPS server") restartHTTPS = true @@ -300,6 +347,9 @@ func unmarshalTLS(r *http.Request) (tlsConfig, error) { return data, errorx.Decorate(err, "Failed to base64-decode certificate chain") } data.CertificateChain = string(certPEM) + if data.CertificatePath != "" { + return data, fmt.Errorf("certificate data and file can't be set together") + } } if data.PrivateKey != "" { @@ -309,6 +359,9 @@ func unmarshalTLS(r *http.Request) (tlsConfig, error) { } data.PrivateKey = string(keyPEM) + if data.PrivateKeyPath != "" { + return data, fmt.Errorf("private key data and file can't be set together") + } } return data, nil diff --git a/home/home.go b/home/home.go index 54a3b0a5..0ae64459 100644 --- a/home/home.go +++ b/home/home.go @@ -218,13 +218,13 @@ func httpServerLoop() { // this mechanism doesn't let us through until all conditions are met for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || - config.TLS.PrivateKey == "" || - config.TLS.CertificateChain == "" { // sleep until necessary data is supplied + len(config.TLS.PrivateKeyData) == 0 || + len(config.TLS.CertificateChainData) == 0 { // sleep until necessary data is supplied config.httpsServer.cond.Wait() } address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS)) // validate current TLS config and update warnings (it could have been loaded from file) - data := validateCertificates(config.TLS.CertificateChain, config.TLS.PrivateKey, config.TLS.ServerName) + data := validateCertificates(string(config.TLS.CertificateChainData), string(config.TLS.PrivateKeyData), config.TLS.ServerName) if !data.ValidPair { cleanupAlways() log.Fatal(data.WarningValidation) @@ -235,10 +235,10 @@ func httpServerLoop() { // prepare certs for HTTPS server // important -- they have to be copies, otherwise changing the contents in config.TLS will break encryption for in-flight requests - certchain := make([]byte, len(config.TLS.CertificateChain)) - copy(certchain, []byte(config.TLS.CertificateChain)) - privatekey := make([]byte, len(config.TLS.PrivateKey)) - copy(privatekey, []byte(config.TLS.PrivateKey)) + certchain := make([]byte, len(config.TLS.CertificateChainData)) + copy(certchain, config.TLS.CertificateChainData) + privatekey := make([]byte, len(config.TLS.PrivateKeyData)) + copy(privatekey, config.TLS.PrivateKeyData) cert, err := tls.X509KeyPair(certchain, privatekey) if err != nil { cleanupAlways()