* TLS is now a separate module (logically)
This commit is contained in:
parent
8e4bc29103
commit
db30f27c8f
@ -71,7 +71,6 @@ Contents:
|
|||||||
![](doc/agh-arch.png)
|
![](doc/agh-arch.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## First startup
|
## First startup
|
||||||
|
|
||||||
The first application startup is detected when there's no .yaml configuration file.
|
The first application startup is detected when there's no .yaml configuration file.
|
||||||
|
@ -51,7 +51,7 @@ type configuration struct {
|
|||||||
WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
|
WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
|
||||||
|
|
||||||
DNS dnsConfig `yaml:"dns"`
|
DNS dnsConfig `yaml:"dns"`
|
||||||
TLS tlsConfig `yaml:"tls"`
|
TLS tlsConfigSettings `yaml:"tls"`
|
||||||
|
|
||||||
Filters []filter `yaml:"filters"`
|
Filters []filter `yaml:"filters"`
|
||||||
WhitelistFilters []filter `yaml:"whitelist_filters"`
|
WhitelistFilters []filter `yaml:"whitelist_filters"`
|
||||||
@ -101,33 +101,6 @@ type tlsConfigSettings struct {
|
|||||||
dnsforward.TLSConfig `yaml:",inline" json:",inline"`
|
dnsforward.TLSConfig `yaml:",inline" json:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// field ordering is not important -- these are for API and are recalculated on each run
|
|
||||||
type tlsConfigStatus struct {
|
|
||||||
ValidCert bool `yaml:"-" json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
|
|
||||||
ValidChain bool `yaml:"-" json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
|
|
||||||
Subject string `yaml:"-" json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain
|
|
||||||
Issuer string `yaml:"-" json:"issuer,omitempty"` // Issuer is the issuer of the first certificate in the chain
|
|
||||||
NotBefore time.Time `yaml:"-" json:"not_before,omitempty"` // NotBefore is the NotBefore field of the first certificate in the chain
|
|
||||||
NotAfter time.Time `yaml:"-" json:"not_after,omitempty"` // NotAfter is the NotAfter field of the first certificate in the chain
|
|
||||||
DNSNames []string `yaml:"-" json:"dns_names"` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain
|
|
||||||
|
|
||||||
// key status
|
|
||||||
ValidKey bool `yaml:"-" json:"valid_key"` // ValidKey is true if the key is a valid private key
|
|
||||||
KeyType string `yaml:"-" json:"key_type,omitempty"` // KeyType is one of RSA or ECDSA
|
|
||||||
|
|
||||||
// is usable? set by validator
|
|
||||||
ValidPair bool `yaml:"-" json:"valid_pair"` // ValidPair is true if both certificate and private key are correct
|
|
||||||
|
|
||||||
// warnings
|
|
||||||
WarningValidation string `yaml:"-" json:"warning_validation,omitempty"` // WarningValidation is a validation warning message with the issue description
|
|
||||||
}
|
|
||||||
|
|
||||||
// field ordering is important -- yaml fields will mirror ordering from here
|
|
||||||
type tlsConfig struct {
|
|
||||||
tlsConfigSettings `yaml:",inline" json:",inline"`
|
|
||||||
tlsConfigStatus `yaml:"-" json:",inline"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize to default values, will be changed later when reading config or parsing command line
|
// initialize to default values, will be changed later when reading config or parsing command line
|
||||||
var config = configuration{
|
var config = configuration{
|
||||||
BindPort: 3000,
|
BindPort: 3000,
|
||||||
@ -147,12 +120,10 @@ var config = configuration{
|
|||||||
FilteringEnabled: true, // whether or not use filter lists
|
FilteringEnabled: true, // whether or not use filter lists
|
||||||
FiltersUpdateIntervalHours: 24,
|
FiltersUpdateIntervalHours: 24,
|
||||||
},
|
},
|
||||||
TLS: tlsConfig{
|
TLS: tlsConfigSettings{
|
||||||
tlsConfigSettings: tlsConfigSettings{
|
|
||||||
PortHTTPS: 443,
|
PortHTTPS: 443,
|
||||||
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
|
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
|
||||||
},
|
},
|
||||||
},
|
|
||||||
DHCP: dhcpd.ServerConfig{
|
DHCP: dhcpd.ServerConfig{
|
||||||
LeaseDuration: 86400,
|
LeaseDuration: 86400,
|
||||||
ICMPTimeout: 1000,
|
ICMPTimeout: 1000,
|
||||||
@ -225,12 +196,6 @@ func parseConfig() error {
|
|||||||
config.DNS.FiltersUpdateIntervalHours = 24
|
config.DNS.FiltersUpdateIntervalHours = 24
|
||||||
}
|
}
|
||||||
|
|
||||||
status := tlsConfigStatus{}
|
|
||||||
if !tlsLoadConfig(&config.TLS, &status) {
|
|
||||||
log.Error("%s", status.WarningValidation)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,6 +224,11 @@ func (c *configuration) write() error {
|
|||||||
if Context.auth != nil {
|
if Context.auth != nil {
|
||||||
config.Users = Context.auth.GetUsers()
|
config.Users = Context.auth.GetUsers()
|
||||||
}
|
}
|
||||||
|
if Context.tls != nil {
|
||||||
|
tlsConf := tlsConfigSettings{}
|
||||||
|
Context.tls.WriteDiskConfig(&tlsConf)
|
||||||
|
config.TLS = tlsConf
|
||||||
|
}
|
||||||
|
|
||||||
if Context.stats != nil {
|
if Context.stats != nil {
|
||||||
sdc := stats.DiskConfig{}
|
sdc := stats.DiskConfig{}
|
||||||
@ -308,13 +278,3 @@ func (c *configuration) write() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAllConfigs() error {
|
|
||||||
err := config.write()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Couldn't write config: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -26,9 +26,6 @@ func returnOK(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpOK(r *http.Request, w http.ResponseWriter) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpError(w http.ResponseWriter, code int, format string, args ...interface{}) {
|
func httpError(w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||||
text := fmt.Sprintf(format, args...)
|
text := fmt.Sprintf(format, args...)
|
||||||
log.Info(text)
|
log.Info(text)
|
||||||
@ -38,15 +35,6 @@ func httpError(w http.ResponseWriter, code int, format string, args ...interface
|
|||||||
// ---------------
|
// ---------------
|
||||||
// dns run control
|
// dns run control
|
||||||
// ---------------
|
// ---------------
|
||||||
func writeAllConfigsAndReloadDNS() error {
|
|
||||||
err := writeAllConfigs()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Couldn't write all configs: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return reconfigureDNSServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addDNSAddress(dnsAddresses *[]string, addr string) {
|
func addDNSAddress(dnsAddresses *[]string, addr string) {
|
||||||
if config.DNS.Port != 53 {
|
if config.DNS.Port != 53 {
|
||||||
addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
|
addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
|
||||||
@ -143,23 +131,6 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
|||||||
_, _ = w.Write(data)
|
_, _ = w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------
|
|
||||||
// DNS-over-HTTPS
|
|
||||||
// --------------
|
|
||||||
func handleDOH(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !config.TLS.AllowUnencryptedDOH && r.TLS == nil {
|
|
||||||
httpError(w, http.StatusNotFound, "Not Found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isRunning() {
|
|
||||||
httpError(w, http.StatusInternalServerError, "DNS server is not running")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Context.dnsServer.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------
|
// ------------------------
|
||||||
// registration of handlers
|
// registration of handlers
|
||||||
// ------------------------
|
// ------------------------
|
||||||
@ -171,8 +142,6 @@ func registerControlHandlers() {
|
|||||||
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
||||||
|
|
||||||
httpRegister("GET", "/control/profile", handleGetProfile)
|
httpRegister("GET", "/control/profile", handleGetProfile)
|
||||||
|
|
||||||
RegisterTLSHandlers()
|
|
||||||
RegisterAuthHandlers()
|
RegisterAuthHandlers()
|
||||||
|
|
||||||
http.HandleFunc("/dns-query", postInstall(handleDOH))
|
http.HandleFunc("/dns-query", postInstall(handleDOH))
|
||||||
@ -265,7 +234,7 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enforce https?
|
// enforce https?
|
||||||
if config.TLS.ForceHTTPS && r.TLS == nil && config.TLS.Enabled && config.TLS.PortHTTPS != 0 && Context.web.httpsServer.server != nil {
|
if r.TLS == nil && Context.web.forceHTTPS && Context.web.httpsServer.server != nil {
|
||||||
// yes, and we want host from host:port
|
// yes, and we want host from host:port
|
||||||
host, _, err := net.SplitHostPort(r.Host)
|
host, _, err := net.SplitHostPort(r.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -275,7 +244,7 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res
|
|||||||
// construct new URL to redirect to
|
// construct new URL to redirect to
|
||||||
newURL := url.URL{
|
newURL := url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: net.JoinHostPort(host, strconv.Itoa(config.TLS.PortHTTPS)),
|
Host: net.JoinHostPort(host, strconv.Itoa(Context.web.portHTTPS)),
|
||||||
Path: r.URL.Path,
|
Path: r.URL.Path,
|
||||||
RawQuery: r.URL.RawQuery,
|
RawQuery: r.URL.RawQuery,
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ type netInterfaceJSON struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get initial installation settings
|
// Get initial installation settings
|
||||||
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
||||||
data := firstRunData{}
|
data := firstRunData{}
|
||||||
data.WebPort = 80
|
data.WebPort = 80
|
||||||
data.DNSPort = 53
|
data.DNSPort = 53
|
||||||
@ -93,7 +93,7 @@ type checkConfigResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if ports are available, respond with results
|
// Check if ports are available, respond with results
|
||||||
func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
|
func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
reqData := checkConfigReq{}
|
reqData := checkConfigReq{}
|
||||||
respData := checkConfigResp{}
|
respData := checkConfigResp{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&reqData)
|
err := json.NewDecoder(r.Body).Decode(&reqData)
|
||||||
@ -275,7 +275,7 @@ func copyInstallSettings(dst *configuration, src *configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply new configuration, start DNS server, restart Web server
|
// Apply new configuration, start DNS server, restart Web server
|
||||||
func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
newSettings := applyConfigReq{}
|
newSettings := applyConfigReq{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&newSettings)
|
err := json.NewDecoder(r.Body).Decode(&newSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -325,22 +325,11 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
|||||||
config.DNS.BindHost = newSettings.DNS.IP
|
config.DNS.BindHost = newSettings.DNS.IP
|
||||||
config.DNS.Port = newSettings.DNS.Port
|
config.DNS.Port = newSettings.DNS.Port
|
||||||
|
|
||||||
err = initDNSServer()
|
err = StartMods()
|
||||||
var err2 error
|
if err != nil {
|
||||||
if err == nil {
|
|
||||||
err2 = startDNSServer()
|
|
||||||
if err2 != nil {
|
|
||||||
closeDNSServer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil || err2 != nil {
|
|
||||||
Context.firstRun = true
|
Context.firstRun = true
|
||||||
copyInstallSettings(&config, &curConfig)
|
copyInstallSettings(&config, &curConfig)
|
||||||
if err != nil {
|
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||||
httpError(w, http.StatusInternalServerError, "Couldn't initialize DNS server: %s", err)
|
|
||||||
} else {
|
|
||||||
httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err2)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,8 +358,8 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
|||||||
returnOK(w)
|
returnOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerInstallHandlers() {
|
func (web *Web) registerInstallHandlers() {
|
||||||
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(handleInstallGetAddresses)))
|
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||||
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(handleInstallCheckConfig)))
|
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||||
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(handleInstallConfigure)))
|
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
|
||||||
}
|
}
|
||||||
|
14
home/dns.go
14
home/dns.go
@ -156,12 +156,18 @@ func generateServerConfig() dnsforward.ServerConfig {
|
|||||||
OnDNSRequest: onDNSRequest,
|
OnDNSRequest: onDNSRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.TLS.Enabled {
|
tlsConf := tlsConfigSettings{}
|
||||||
newconfig.TLSConfig = config.TLS.TLSConfig
|
Context.tls.WriteDiskConfig(&tlsConf)
|
||||||
if config.TLS.PortDNSOverTLS != 0 {
|
if tlsConf.Enabled {
|
||||||
newconfig.TLSListenAddr = &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.TLS.PortDNSOverTLS}
|
newconfig.TLSConfig = tlsConf.TLSConfig
|
||||||
|
if tlsConf.PortDNSOverTLS != 0 {
|
||||||
|
newconfig.TLSListenAddr = &net.TCPAddr{
|
||||||
|
IP: net.ParseIP(config.DNS.BindHost),
|
||||||
|
Port: tlsConf.PortDNSOverTLS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH
|
||||||
|
}
|
||||||
newconfig.TLSv12Roots = Context.tlsRoots
|
newconfig.TLSv12Roots = Context.tlsRoots
|
||||||
|
|
||||||
newconfig.FilterHandler = applyAdditionalFiltering
|
newconfig.FilterHandler = applyAdditionalFiltering
|
||||||
|
46
home/home.go
46
home/home.go
@ -65,8 +65,9 @@ type homeContext struct {
|
|||||||
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
|
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
|
||||||
dhcpServer *dhcpd.Server // DHCP module
|
dhcpServer *dhcpd.Server // DHCP module
|
||||||
auth *Auth // HTTP authentication module
|
auth *Auth // HTTP authentication module
|
||||||
filters Filtering
|
filters Filtering // DNS filtering module
|
||||||
web *Web
|
web *Web // Web (HTTP, HTTPS) module
|
||||||
|
tls *TLSMod // TLS module
|
||||||
|
|
||||||
// Runtime properties
|
// Runtime properties
|
||||||
// --
|
// --
|
||||||
@ -119,6 +120,7 @@ func Main(version string, channel string, armVer string) {
|
|||||||
switch sig {
|
switch sig {
|
||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
Context.clients.Reload()
|
Context.clients.Reload()
|
||||||
|
Context.tls.Reload()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
cleanup()
|
cleanup()
|
||||||
@ -247,11 +249,15 @@ func run(args options) {
|
|||||||
}
|
}
|
||||||
config.Users = nil
|
config.Users = nil
|
||||||
|
|
||||||
|
Context.tls = tlsCreate(config.TLS)
|
||||||
|
if Context.tls == nil {
|
||||||
|
log.Fatalf("Can't initialize TLS module")
|
||||||
|
}
|
||||||
|
|
||||||
webConf := WebConfig{
|
webConf := WebConfig{
|
||||||
firstRun: Context.firstRun,
|
firstRun: Context.firstRun,
|
||||||
BindHost: config.BindHost,
|
BindHost: config.BindHost,
|
||||||
BindPort: config.BindPort,
|
BindPort: config.BindPort,
|
||||||
TLS: config.TLS,
|
|
||||||
}
|
}
|
||||||
Context.web = CreateWeb(&webConf)
|
Context.web = CreateWeb(&webConf)
|
||||||
if Context.web == nil {
|
if Context.web == nil {
|
||||||
@ -263,6 +269,8 @@ func run(args options) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%s", err)
|
log.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
Context.tls.Start()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := startDNSServer()
|
err := startDNSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -282,6 +290,23 @@ func run(args options) {
|
|||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartMods - initialize and start DNS after installation
|
||||||
|
func StartMods() error {
|
||||||
|
err := initDNSServer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Context.tls.Start()
|
||||||
|
|
||||||
|
err = startDNSServer()
|
||||||
|
if err != nil {
|
||||||
|
closeDNSServer()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the current user has root (administrator) rights
|
// Check if the current user has root (administrator) rights
|
||||||
// and if not, ask and try to run as root
|
// and if not, ask and try to run as root
|
||||||
func requireAdminRights() {
|
func requireAdminRights() {
|
||||||
@ -408,6 +433,11 @@ func cleanup() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Couldn't stop DHCP server: %s", err)
|
log.Error("Couldn't stop DHCP server: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Context.tls != nil {
|
||||||
|
Context.tls.Close()
|
||||||
|
Context.tls = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is called before application exits
|
// This function is called before application exits
|
||||||
@ -528,11 +558,13 @@ func loadOptions() options {
|
|||||||
func printHTTPAddresses(proto string) {
|
func printHTTPAddresses(proto string) {
|
||||||
var address string
|
var address string
|
||||||
|
|
||||||
if proto == "https" && config.TLS.ServerName != "" {
|
tlsConf := tlsConfigSettings{}
|
||||||
if config.TLS.PortHTTPS == 443 {
|
Context.tls.WriteDiskConfig(&tlsConf)
|
||||||
log.Printf("Go to https://%s", config.TLS.ServerName)
|
if proto == "https" && tlsConf.ServerName != "" {
|
||||||
|
if tlsConf.PortHTTPS == 443 {
|
||||||
|
log.Printf("Go to https://%s", tlsConf.ServerName)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Go to https://%s:%d", config.TLS.ServerName, config.TLS.PortHTTPS)
|
log.Printf("Go to https://%s:%d", tlsConf.ServerName, tlsConf.PortHTTPS)
|
||||||
}
|
}
|
||||||
} else if config.BindHost == "0.0.0.0" {
|
} else if config.BindHost == "0.0.0.0" {
|
||||||
log.Println("AdGuard Home is available on the following addresses:")
|
log.Println("AdGuard Home is available on the following addresses:")
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// Control: TLS configuring handlers
|
|
||||||
|
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
@ -16,18 +13,125 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/joomcode/errorx"
|
"github.com/joomcode/errorx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var tlsWebHandlersRegistered = false
|
||||||
|
|
||||||
|
// TLSMod - TLS module object
|
||||||
|
type TLSMod struct {
|
||||||
|
certLastMod time.Time // last modification time of the certificate file
|
||||||
|
conf tlsConfigSettings
|
||||||
|
confLock sync.Mutex
|
||||||
|
status tlsConfigStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create TLS module
|
||||||
|
func tlsCreate(conf tlsConfigSettings) *TLSMod {
|
||||||
|
t := &TLSMod{}
|
||||||
|
t.conf = conf
|
||||||
|
if t.conf.Enabled {
|
||||||
|
if !t.load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t.setCertFileTime()
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSMod) load() bool {
|
||||||
|
if !tlsLoadConfig(&t.conf, &t.status) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate current TLS config and update warnings (it could have been loaded from file)
|
||||||
|
data := validateCertificates(string(t.conf.CertificateChainData), string(t.conf.PrivateKeyData), t.conf.ServerName)
|
||||||
|
if !data.ValidPair {
|
||||||
|
log.Error(data.WarningValidation)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t.status = data
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close - close module
|
||||||
|
func (t *TLSMod) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteDiskConfig - write config
|
||||||
|
func (t *TLSMod) WriteDiskConfig(conf *tlsConfigSettings) {
|
||||||
|
t.confLock.Lock()
|
||||||
|
*conf = t.conf
|
||||||
|
t.confLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSMod) setCertFileTime() {
|
||||||
|
if len(t.conf.CertificatePath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(t.conf.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("TLS: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.certLastMod = fi.ModTime().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start - start the module
|
||||||
|
func (t *TLSMod) Start() {
|
||||||
|
if !tlsWebHandlersRegistered {
|
||||||
|
tlsWebHandlersRegistered = true
|
||||||
|
t.registerWebHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.confLock.Lock()
|
||||||
|
tlsConf := t.conf
|
||||||
|
t.confLock.Unlock()
|
||||||
|
Context.web.TLSConfigChanged(tlsConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload - reload certificate file
|
||||||
|
func (t *TLSMod) Reload() {
|
||||||
|
t.confLock.Lock()
|
||||||
|
tlsConf := t.conf
|
||||||
|
t.confLock.Unlock()
|
||||||
|
|
||||||
|
if !tlsConf.Enabled || len(tlsConf.CertificatePath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(tlsConf.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("TLS: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fi.ModTime().UTC().Equal(t.certLastMod) {
|
||||||
|
log.Debug("TLS: certificate file isn't modified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug("TLS: certificate file is modified")
|
||||||
|
|
||||||
|
t.confLock.Lock()
|
||||||
|
r := t.load()
|
||||||
|
t.confLock.Unlock()
|
||||||
|
if !r {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.certLastMod = fi.ModTime().UTC()
|
||||||
|
|
||||||
|
_ = reconfigureDNSServer()
|
||||||
|
Context.web.TLSConfigChanged(tlsConf)
|
||||||
|
}
|
||||||
|
|
||||||
// Set certificate and private key data
|
// Set certificate and private key data
|
||||||
func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool {
|
func tlsLoadConfig(tls *tlsConfigSettings, status *tlsConfigStatus) bool {
|
||||||
tls.CertificateChainData = []byte(tls.CertificateChain)
|
tls.CertificateChainData = []byte(tls.CertificateChain)
|
||||||
tls.PrivateKeyData = []byte(tls.PrivateKey)
|
tls.PrivateKeyData = []byte(tls.PrivateKey)
|
||||||
|
|
||||||
@ -61,98 +165,115 @@ func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterTLSHandlers registers HTTP handlers for TLS configuration
|
type tlsConfigStatus struct {
|
||||||
func RegisterTLSHandlers() {
|
ValidCert bool `json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
|
||||||
httpRegister(http.MethodGet, "/control/tls/status", handleTLSStatus)
|
ValidChain bool `json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
|
||||||
httpRegister(http.MethodPost, "/control/tls/configure", handleTLSConfigure)
|
Subject string `json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain
|
||||||
httpRegister(http.MethodPost, "/control/tls/validate", handleTLSValidate)
|
Issuer string `json:"issuer,omitempty"` // Issuer is the issuer of the first certificate in the chain
|
||||||
|
NotBefore time.Time `json:"not_before,omitempty"` // NotBefore is the NotBefore field of the first certificate in the chain
|
||||||
|
NotAfter time.Time `json:"not_after,omitempty"` // NotAfter is the NotAfter field of the first certificate in the chain
|
||||||
|
DNSNames []string `json:"dns_names"` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain
|
||||||
|
|
||||||
|
// key status
|
||||||
|
ValidKey bool `json:"valid_key"` // ValidKey is true if the key is a valid private key
|
||||||
|
KeyType string `json:"key_type,omitempty"` // KeyType is one of RSA or ECDSA
|
||||||
|
|
||||||
|
// is usable? set by validator
|
||||||
|
ValidPair bool `json:"valid_pair"` // ValidPair is true if both certificate and private key are correct
|
||||||
|
|
||||||
|
// warnings
|
||||||
|
WarningValidation string `json:"warning_validation,omitempty"` // WarningValidation is a validation warning message with the issue description
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTLSStatus(w http.ResponseWriter, r *http.Request) {
|
// field ordering is important -- yaml fields will mirror ordering from here
|
||||||
marshalTLS(w, config.TLS)
|
type tlsConfig struct {
|
||||||
|
tlsConfigSettings `json:",inline"`
|
||||||
|
tlsConfigStatus `json:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTLSValidate(w http.ResponseWriter, r *http.Request) {
|
func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
data, err := unmarshalTLS(r)
|
t.confLock.Lock()
|
||||||
if err != nil {
|
data := tlsConfig{
|
||||||
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
|
tlsConfigSettings: t.conf,
|
||||||
return
|
tlsConfigStatus: t.status,
|
||||||
}
|
}
|
||||||
|
t.confLock.Unlock()
|
||||||
// check if port is available
|
|
||||||
// BUT: if we are already using this port, no need
|
|
||||||
alreadyRunning := false
|
|
||||||
if Context.web.httpsServer.server != nil {
|
|
||||||
alreadyRunning = true
|
|
||||||
}
|
|
||||||
if !alreadyRunning {
|
|
||||||
err = util.CheckPortAvailable(config.BindHost, data.PortHTTPS)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
|
func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
setts, err := unmarshalTLS(r)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !WebCheckPortAvailable(setts.PortHTTPS) {
|
||||||
|
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", setts.PortHTTPS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := tlsConfigStatus{}
|
||||||
|
if tlsLoadConfig(&setts, &status) {
|
||||||
|
status = validateCertificates(string(setts.CertificateChainData), string(setts.PrivateKeyData), setts.ServerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := tlsConfig{
|
||||||
|
tlsConfigSettings: setts,
|
||||||
|
tlsConfigStatus: status,
|
||||||
|
}
|
||||||
|
marshalTLS(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
data, err := unmarshalTLS(r)
|
data, err := unmarshalTLS(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
|
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if port is available
|
if !WebCheckPortAvailable(data.PortHTTPS) {
|
||||||
// BUT: if we are already using this port, no need
|
|
||||||
alreadyRunning := false
|
|
||||||
if Context.web.httpsServer.server != nil {
|
|
||||||
alreadyRunning = true
|
|
||||||
}
|
|
||||||
if !alreadyRunning {
|
|
||||||
err = util.CheckPortAvailable(config.BindHost, data.PortHTTPS)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
|
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
status := tlsConfigStatus{}
|
status := tlsConfigStatus{}
|
||||||
if !tlsLoadConfig(&data, &status) {
|
if !tlsLoadConfig(&data, &status) {
|
||||||
data.tlsConfigStatus = status
|
data2 := tlsConfig{
|
||||||
marshalTLS(w, data)
|
tlsConfigSettings: data,
|
||||||
|
tlsConfigStatus: t.status,
|
||||||
|
}
|
||||||
|
marshalTLS(w, data2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data.tlsConfigStatus = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
|
status = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
|
||||||
restartHTTPS := false
|
restartHTTPS := false
|
||||||
if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) {
|
t.confLock.Lock()
|
||||||
|
if !reflect.DeepEqual(t.conf, data) {
|
||||||
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
|
||||||
}
|
}
|
||||||
config.TLS = data
|
t.conf = data
|
||||||
err = writeAllConfigsAndReloadDNS()
|
t.status = status
|
||||||
|
t.confLock.Unlock()
|
||||||
|
t.setCertFileTime()
|
||||||
|
onConfigModified()
|
||||||
|
err = reconfigureDNSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "Couldn't write config file: %s", err)
|
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
marshalTLS(w, data)
|
data2 := tlsConfig{
|
||||||
|
tlsConfigSettings: data,
|
||||||
|
tlsConfigStatus: t.status,
|
||||||
|
}
|
||||||
|
marshalTLS(w, data2)
|
||||||
// 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 {
|
if restartHTTPS {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server
|
time.Sleep(time.Second) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server
|
||||||
Context.web.httpsServer.cond.L.Lock()
|
Context.web.TLSConfigChanged(data)
|
||||||
Context.web.httpsServer.cond.Broadcast()
|
|
||||||
if Context.web.httpsServer.server != nil {
|
|
||||||
Context.web.httpsServer.server.Shutdown(context.TODO())
|
|
||||||
}
|
|
||||||
Context.web.httpsServer.cond.L.Unlock()
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,8 +458,8 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unmarshalTLS handles base64-encoded certificates transparently
|
// unmarshalTLS handles base64-encoded certificates transparently
|
||||||
func unmarshalTLS(r *http.Request) (tlsConfig, error) {
|
func unmarshalTLS(r *http.Request) (tlsConfigSettings, error) {
|
||||||
data := tlsConfig{}
|
data := tlsConfigSettings{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&data)
|
err := json.NewDecoder(r.Body).Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, errorx.Decorate(err, "Failed to parse new TLS config json")
|
return data, errorx.Decorate(err, "Failed to parse new TLS config json")
|
||||||
@ -389,3 +510,10 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// registerWebHandlers registers HTTP handlers for TLS configuration
|
||||||
|
func (t *TLSMod) registerWebHandlers() {
|
||||||
|
httpRegister("GET", "/control/tls/status", t.handleTLSStatus)
|
||||||
|
httpRegister("POST", "/control/tls/configure", t.handleTLSConfigure)
|
||||||
|
httpRegister("POST", "/control/tls/validate", t.handleTLSValidate)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user