mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-25 06:25:44 +03:00
+ config: add certificate_path, private_key_path
* POST /control/tls/configure: support certificate_path and private_key_path
This commit is contained in:
parent
c847df9976
commit
24bb708b21
6 changed files with 142 additions and 14 deletions
|
@ -12,6 +12,9 @@ Contents:
|
||||||
* Updating
|
* Updating
|
||||||
* Get version command
|
* Get version command
|
||||||
* Update command
|
* Update command
|
||||||
|
* TLS
|
||||||
|
* API: Get TLS configuration
|
||||||
|
* API: Set TLS configuration
|
||||||
* Device Names and Per-client Settings
|
* Device Names and Per-client Settings
|
||||||
* Per-client settings
|
* Per-client settings
|
||||||
* Get list of clients
|
* Get list of clients
|
||||||
|
@ -515,6 +518,66 @@ Response:
|
||||||
200 OK
|
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
|
## Device Names and Per-client Settings
|
||||||
|
|
||||||
When a client requests information from DNS server, he's identified by IP address.
|
When a client requests information from DNS server, he's identified by IP address.
|
||||||
|
|
|
@ -108,6 +108,12 @@ type TLSConfig struct {
|
||||||
TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"`
|
TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"`
|
||||||
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` // PEM-encoded certificates chain
|
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` // PEM-encoded certificates chain
|
||||||
PrivateKey string `yaml:"private_key" json:"private_key"` // PEM-encoded private key
|
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.
|
// ServerConfig represents server configuration.
|
||||||
|
@ -216,9 +222,9 @@ func (s *Server) startInternal(config *ServerConfig) error {
|
||||||
|
|
||||||
convertArrayToMap(&s.BlockedHosts, s.conf.BlockedHosts)
|
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
|
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 {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "Failed to parse TLS keypair")
|
return errorx.Decorate(err, "Failed to parse TLS keypair")
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,9 +118,9 @@ func TestDotServer(t *testing.T) {
|
||||||
defer removeDataDir(t)
|
defer removeDataDir(t)
|
||||||
|
|
||||||
s.conf.TLSConfig = TLSConfig{
|
s.conf.TLSConfig = TLSConfig{
|
||||||
TLSListenAddr: &net.TCPAddr{Port: 0},
|
TLSListenAddr: &net.TCPAddr{Port: 0},
|
||||||
CertificateChain: string(certPem),
|
CertificateChainData: certPem,
|
||||||
PrivateKey: string(keyPem),
|
PrivateKeyData: keyPem,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starting the server
|
// Starting the server
|
||||||
|
|
|
@ -279,6 +279,12 @@ func parseConfig() error {
|
||||||
}
|
}
|
||||||
config.Clients = nil
|
config.Clients = nil
|
||||||
|
|
||||||
|
status := tlsConfigStatus{}
|
||||||
|
if !tlsLoadConfig(&config.TLS, &status) {
|
||||||
|
log.Error("%s", status.WarningValidation)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Deduplicate filters
|
// Deduplicate filters
|
||||||
deduplicateFilters()
|
deduplicateFilters()
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -23,6 +24,41 @@ import (
|
||||||
"github.com/joomcode/errorx"
|
"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
|
// RegisterTLSHandlers registers HTTP handlers for TLS configuration
|
||||||
func RegisterTLSHandlers() {
|
func RegisterTLSHandlers() {
|
||||||
httpRegister(http.MethodGet, "/control/tls/status", handleTLSStatus)
|
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)
|
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
|
restartHTTPS := false
|
||||||
data.tlsConfigStatus = validateCertificates(data.CertificateChain, data.PrivateKey, data.ServerName)
|
|
||||||
if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) {
|
if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) {
|
||||||
log.Printf("tls config settings have changed, will restart HTTPS server")
|
log.Printf("tls config settings have changed, will restart HTTPS server")
|
||||||
restartHTTPS = true
|
restartHTTPS = true
|
||||||
|
@ -300,6 +347,9 @@ func unmarshalTLS(r *http.Request) (tlsConfig, error) {
|
||||||
return data, errorx.Decorate(err, "Failed to base64-decode certificate chain")
|
return data, errorx.Decorate(err, "Failed to base64-decode certificate chain")
|
||||||
}
|
}
|
||||||
data.CertificateChain = string(certPEM)
|
data.CertificateChain = string(certPEM)
|
||||||
|
if data.CertificatePath != "" {
|
||||||
|
return data, fmt.Errorf("certificate data and file can't be set together")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.PrivateKey != "" {
|
if data.PrivateKey != "" {
|
||||||
|
@ -309,6 +359,9 @@ func unmarshalTLS(r *http.Request) (tlsConfig, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data.PrivateKey = string(keyPEM)
|
data.PrivateKey = string(keyPEM)
|
||||||
|
if data.PrivateKeyPath != "" {
|
||||||
|
return data, fmt.Errorf("private key data and file can't be set together")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|
14
home/home.go
14
home/home.go
|
@ -218,13 +218,13 @@ func httpServerLoop() {
|
||||||
// this mechanism doesn't let us through until all conditions are met
|
// this mechanism doesn't let us through until all conditions are met
|
||||||
for config.TLS.Enabled == false ||
|
for config.TLS.Enabled == false ||
|
||||||
config.TLS.PortHTTPS == 0 ||
|
config.TLS.PortHTTPS == 0 ||
|
||||||
config.TLS.PrivateKey == "" ||
|
len(config.TLS.PrivateKeyData) == 0 ||
|
||||||
config.TLS.CertificateChain == "" { // sleep until necessary data is supplied
|
len(config.TLS.CertificateChainData) == 0 { // sleep until necessary data is supplied
|
||||||
config.httpsServer.cond.Wait()
|
config.httpsServer.cond.Wait()
|
||||||
}
|
}
|
||||||
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 := 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 {
|
if !data.ValidPair {
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
log.Fatal(data.WarningValidation)
|
log.Fatal(data.WarningValidation)
|
||||||
|
@ -235,10 +235,10 @@ func httpServerLoop() {
|
||||||
|
|
||||||
// prepare certs for HTTPS server
|
// 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
|
// 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))
|
certchain := make([]byte, len(config.TLS.CertificateChainData))
|
||||||
copy(certchain, []byte(config.TLS.CertificateChain))
|
copy(certchain, config.TLS.CertificateChainData)
|
||||||
privatekey := make([]byte, len(config.TLS.PrivateKey))
|
privatekey := make([]byte, len(config.TLS.PrivateKeyData))
|
||||||
copy(privatekey, []byte(config.TLS.PrivateKey))
|
copy(privatekey, config.TLS.PrivateKeyData)
|
||||||
cert, err := tls.X509KeyPair(certchain, privatekey)
|
cert, err := tls.X509KeyPair(certchain, privatekey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
|
|
Loading…
Reference in a new issue