mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-12-15 11:22:49 +03:00
* 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)
|
||||
|
||||
|
||||
|
||||
## First startup
|
||||
|
||||
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"`
|
||||
|
||||
DNS dnsConfig `yaml:"dns"`
|
||||
TLS tlsConfig `yaml:"tls"`
|
||||
TLS tlsConfigSettings `yaml:"tls"`
|
||||
|
||||
Filters []filter `yaml:"filters"`
|
||||
WhitelistFilters []filter `yaml:"whitelist_filters"`
|
||||
@ -101,33 +101,6 @@ type tlsConfigSettings struct {
|
||||
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
|
||||
var config = configuration{
|
||||
BindPort: 3000,
|
||||
@ -147,12 +120,10 @@ var config = configuration{
|
||||
FilteringEnabled: true, // whether or not use filter lists
|
||||
FiltersUpdateIntervalHours: 24,
|
||||
},
|
||||
TLS: tlsConfig{
|
||||
tlsConfigSettings: tlsConfigSettings{
|
||||
TLS: tlsConfigSettings{
|
||||
PortHTTPS: 443,
|
||||
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
|
||||
},
|
||||
},
|
||||
DHCP: dhcpd.ServerConfig{
|
||||
LeaseDuration: 86400,
|
||||
ICMPTimeout: 1000,
|
||||
@ -225,12 +196,6 @@ func parseConfig() error {
|
||||
config.DNS.FiltersUpdateIntervalHours = 24
|
||||
}
|
||||
|
||||
status := tlsConfigStatus{}
|
||||
if !tlsLoadConfig(&config.TLS, &status) {
|
||||
log.Error("%s", status.WarningValidation)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -259,6 +224,11 @@ func (c *configuration) write() error {
|
||||
if Context.auth != nil {
|
||||
config.Users = Context.auth.GetUsers()
|
||||
}
|
||||
if Context.tls != nil {
|
||||
tlsConf := tlsConfigSettings{}
|
||||
Context.tls.WriteDiskConfig(&tlsConf)
|
||||
config.TLS = tlsConf
|
||||
}
|
||||
|
||||
if Context.stats != nil {
|
||||
sdc := stats.DiskConfig{}
|
||||
@ -308,13 +278,3 @@ func (c *configuration) write() error {
|
||||
|
||||
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{}) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
log.Info(text)
|
||||
@ -38,15 +35,6 @@ func httpError(w http.ResponseWriter, code int, format string, args ...interface
|
||||
// ---------------
|
||||
// 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) {
|
||||
if config.DNS.Port != 53 {
|
||||
addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
|
||||
@ -143,23 +131,6 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = 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
|
||||
// ------------------------
|
||||
@ -171,8 +142,6 @@ func registerControlHandlers() {
|
||||
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
||||
|
||||
httpRegister("GET", "/control/profile", handleGetProfile)
|
||||
|
||||
RegisterTLSHandlers()
|
||||
RegisterAuthHandlers()
|
||||
|
||||
http.HandleFunc("/dns-query", postInstall(handleDOH))
|
||||
@ -265,7 +234,7 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res
|
||||
}
|
||||
|
||||
// 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
|
||||
host, _, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
@ -275,7 +244,7 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res
|
||||
// construct new URL to redirect to
|
||||
newURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(host, strconv.Itoa(config.TLS.PortHTTPS)),
|
||||
Host: net.JoinHostPort(host, strconv.Itoa(Context.web.portHTTPS)),
|
||||
Path: r.URL.Path,
|
||||
RawQuery: r.URL.RawQuery,
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ type netInterfaceJSON struct {
|
||||
}
|
||||
|
||||
// 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.WebPort = 80
|
||||
data.DNSPort = 53
|
||||
@ -93,7 +93,7 @@ type checkConfigResp struct {
|
||||
}
|
||||
|
||||
// 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{}
|
||||
respData := checkConfigResp{}
|
||||
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
|
||||
func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
newSettings := applyConfigReq{}
|
||||
err := json.NewDecoder(r.Body).Decode(&newSettings)
|
||||
if err != nil {
|
||||
@ -325,22 +325,11 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
config.DNS.BindHost = newSettings.DNS.IP
|
||||
config.DNS.Port = newSettings.DNS.Port
|
||||
|
||||
err = initDNSServer()
|
||||
var err2 error
|
||||
if err == nil {
|
||||
err2 = startDNSServer()
|
||||
if err2 != nil {
|
||||
closeDNSServer()
|
||||
}
|
||||
}
|
||||
if err != nil || err2 != nil {
|
||||
err = StartMods()
|
||||
if err != nil {
|
||||
Context.firstRun = true
|
||||
copyInstallSettings(&config, &curConfig)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Couldn't initialize DNS server: %s", err)
|
||||
} else {
|
||||
httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err2)
|
||||
}
|
||||
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -369,8 +358,8 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
returnOK(w)
|
||||
}
|
||||
|
||||
func registerInstallHandlers() {
|
||||
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(handleInstallGetAddresses)))
|
||||
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(handleInstallCheckConfig)))
|
||||
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(handleInstallConfigure)))
|
||||
func (web *Web) registerInstallHandlers() {
|
||||
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||
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,
|
||||
}
|
||||
|
||||
if config.TLS.Enabled {
|
||||
newconfig.TLSConfig = config.TLS.TLSConfig
|
||||
if config.TLS.PortDNSOverTLS != 0 {
|
||||
newconfig.TLSListenAddr = &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.TLS.PortDNSOverTLS}
|
||||
tlsConf := tlsConfigSettings{}
|
||||
Context.tls.WriteDiskConfig(&tlsConf)
|
||||
if tlsConf.Enabled {
|
||||
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.FilterHandler = applyAdditionalFiltering
|
||||
|
46
home/home.go
46
home/home.go
@ -65,8 +65,9 @@ type homeContext struct {
|
||||
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
|
||||
dhcpServer *dhcpd.Server // DHCP module
|
||||
auth *Auth // HTTP authentication module
|
||||
filters Filtering
|
||||
web *Web
|
||||
filters Filtering // DNS filtering module
|
||||
web *Web // Web (HTTP, HTTPS) module
|
||||
tls *TLSMod // TLS module
|
||||
|
||||
// Runtime properties
|
||||
// --
|
||||
@ -119,6 +120,7 @@ func Main(version string, channel string, armVer string) {
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
Context.clients.Reload()
|
||||
Context.tls.Reload()
|
||||
|
||||
default:
|
||||
cleanup()
|
||||
@ -247,11 +249,15 @@ func run(args options) {
|
||||
}
|
||||
config.Users = nil
|
||||
|
||||
Context.tls = tlsCreate(config.TLS)
|
||||
if Context.tls == nil {
|
||||
log.Fatalf("Can't initialize TLS module")
|
||||
}
|
||||
|
||||
webConf := WebConfig{
|
||||
firstRun: Context.firstRun,
|
||||
BindHost: config.BindHost,
|
||||
BindPort: config.BindPort,
|
||||
TLS: config.TLS,
|
||||
}
|
||||
Context.web = CreateWeb(&webConf)
|
||||
if Context.web == nil {
|
||||
@ -263,6 +269,8 @@ func run(args options) {
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
}
|
||||
Context.tls.Start()
|
||||
|
||||
go func() {
|
||||
err := startDNSServer()
|
||||
if err != nil {
|
||||
@ -282,6 +290,23 @@ func run(args options) {
|
||||
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
|
||||
// and if not, ask and try to run as root
|
||||
func requireAdminRights() {
|
||||
@ -408,6 +433,11 @@ func cleanup() {
|
||||
if err != nil {
|
||||
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
|
||||
@ -528,11 +558,13 @@ func loadOptions() options {
|
||||
func printHTTPAddresses(proto string) {
|
||||
var address string
|
||||
|
||||
if proto == "https" && config.TLS.ServerName != "" {
|
||||
if config.TLS.PortHTTPS == 443 {
|
||||
log.Printf("Go to https://%s", config.TLS.ServerName)
|
||||
tlsConf := tlsConfigSettings{}
|
||||
Context.tls.WriteDiskConfig(&tlsConf)
|
||||
if proto == "https" && tlsConf.ServerName != "" {
|
||||
if tlsConf.PortHTTPS == 443 {
|
||||
log.Printf("Go to https://%s", tlsConf.ServerName)
|
||||
} 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" {
|
||||
log.Println("AdGuard Home is available on the following addresses:")
|
||||
|
@ -1,9 +1,6 @@
|
||||
// Control: TLS configuring handlers
|
||||
|
||||
package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
@ -16,18 +13,125 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/util"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"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
|
||||
func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool {
|
||||
func tlsLoadConfig(tls *tlsConfigSettings, status *tlsConfigStatus) bool {
|
||||
tls.CertificateChainData = []byte(tls.CertificateChain)
|
||||
tls.PrivateKeyData = []byte(tls.PrivateKey)
|
||||
|
||||
@ -61,98 +165,115 @@ func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RegisterTLSHandlers registers HTTP handlers for TLS configuration
|
||||
func RegisterTLSHandlers() {
|
||||
httpRegister(http.MethodGet, "/control/tls/status", handleTLSStatus)
|
||||
httpRegister(http.MethodPost, "/control/tls/configure", handleTLSConfigure)
|
||||
httpRegister(http.MethodPost, "/control/tls/validate", handleTLSValidate)
|
||||
type tlsConfigStatus struct {
|
||||
ValidCert bool `json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
|
||||
ValidChain bool `json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
|
||||
Subject string `json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain
|
||||
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) {
|
||||
marshalTLS(w, config.TLS)
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
type tlsConfig struct {
|
||||
tlsConfigSettings `json:",inline"`
|
||||
tlsConfigStatus `json:",inline"`
|
||||
}
|
||||
|
||||
func handleTLSValidate(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := unmarshalTLS(r)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
|
||||
return
|
||||
func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
|
||||
t.confLock.Lock()
|
||||
data := tlsConfig{
|
||||
tlsConfigSettings: t.conf,
|
||||
tlsConfigStatus: t.status,
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
t.confLock.Unlock()
|
||||
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)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if !WebCheckPortAvailable(data.PortHTTPS) {
|
||||
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
status := tlsConfigStatus{}
|
||||
if !tlsLoadConfig(&data, &status) {
|
||||
data.tlsConfigStatus = status
|
||||
marshalTLS(w, data)
|
||||
data2 := tlsConfig{
|
||||
tlsConfigSettings: data,
|
||||
tlsConfigStatus: t.status,
|
||||
}
|
||||
marshalTLS(w, data2)
|
||||
return
|
||||
}
|
||||
data.tlsConfigStatus = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
|
||||
status = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
|
||||
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")
|
||||
restartHTTPS = true
|
||||
}
|
||||
config.TLS = data
|
||||
err = writeAllConfigsAndReloadDNS()
|
||||
t.conf = data
|
||||
t.status = status
|
||||
t.confLock.Unlock()
|
||||
t.setCertFileTime()
|
||||
onConfigModified()
|
||||
err = reconfigureDNSServer()
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Couldn't write config file: %s", err)
|
||||
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||
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
|
||||
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
|
||||
if restartHTTPS {
|
||||
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
|
||||
Context.web.httpsServer.cond.L.Lock()
|
||||
Context.web.httpsServer.cond.Broadcast()
|
||||
if Context.web.httpsServer.server != nil {
|
||||
Context.web.httpsServer.server.Shutdown(context.TODO())
|
||||
}
|
||||
Context.web.httpsServer.cond.L.Unlock()
|
||||
Context.web.TLSConfigChanged(data)
|
||||
}()
|
||||
}
|
||||
}
|
||||
@ -337,8 +458,8 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, string, error) {
|
||||
}
|
||||
|
||||
// unmarshalTLS handles base64-encoded certificates transparently
|
||||
func unmarshalTLS(r *http.Request) (tlsConfig, error) {
|
||||
data := tlsConfig{}
|
||||
func unmarshalTLS(r *http.Request) (tlsConfigSettings, error) {
|
||||
data := tlsConfigSettings{}
|
||||
err := json.NewDecoder(r.Body).Decode(&data)
|
||||
if err != nil {
|
||||
return data, errorx.Decorate(err, "Failed to parse new TLS config json")
|
||||
@ -389,3 +510,10 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) {
|
||||
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