@@ -434,6 +452,7 @@ Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func,
isEnabled: PropTypes.bool.isRequired,
+ servePlainDns: PropTypes.bool.isRequired,
certificateChain: PropTypes.string.isRequired,
privateKey: PropTypes.string.isRequired,
certificatePath: PropTypes.string.isRequired,
@@ -467,6 +486,7 @@ const selector = formValueSelector(FORM_NAME.ENCRYPTION);
Form = connect((state) => {
const isEnabled = selector(state, 'enabled');
+ const servePlainDns = selector(state, 'serve_plain_dns');
const certificateChain = selector(state, 'certificate_chain');
const privateKey = selector(state, 'private_key');
const certificatePath = selector(state, 'certificate_path');
@@ -476,6 +496,7 @@ Form = connect((state) => {
const privateKeySaved = selector(state, 'private_key_saved');
return {
isEnabled,
+ servePlainDns,
certificateChain,
privateKey,
certificatePath,
diff --git a/client/src/components/Settings/Encryption/index.js b/client/src/components/Settings/Encryption/index.js
index 4e4cef67253..bcf610c3e3d 100644
--- a/client/src/components/Settings/Encryption/index.js
+++ b/client/src/components/Settings/Encryption/index.js
@@ -25,7 +25,8 @@ class Encryption extends Component {
handleFormChange = debounce((values) => {
const submitValues = this.getSubmitValues(values);
- if (submitValues.enabled) {
+
+ if (submitValues.enabled || submitValues.serve_plain_dns) {
this.props.validateTlsConfig(submitValues);
}
}, DEBOUNCE_TIMEOUT);
@@ -85,6 +86,7 @@ class Encryption extends Component {
certificate_path,
private_key_path,
private_key_saved,
+ serve_plain_dns,
} = encryption;
const initialValues = this.getInitialValues({
@@ -99,6 +101,7 @@ class Encryption extends Component {
certificate_path,
private_key_path,
private_key_saved,
+ serve_plain_dns,
});
return (
diff --git a/client/src/helpers/form.js b/client/src/helpers/form.js
index f58aa830da2..1b7b4997a77 100644
--- a/client/src/helpers/form.js
+++ b/client/src/helpers/form.js
@@ -180,7 +180,7 @@ export const CheckboxField = ({
{!disabled
&& touched
&& error
- &&
{error}}
+ &&
{error}
}
>;
CheckboxField.propTypes = {
diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js
index e9d100a8ee5..76e79d6f2f1 100644
--- a/client/src/helpers/validators.js
+++ b/client/src/helpers/validators.js
@@ -389,3 +389,18 @@ export const validateIPv6Subnet = (value) => {
}
return undefined;
};
+
+/**
+ * @returns {undefined|string}
+ * @param value
+ * @param allValues
+ */
+export const validatePlainDns = (value, allValues) => {
+ const { enabled } = allValues;
+
+ if (!enabled && !value) {
+ return 'encryption_plain_dns_error';
+ }
+
+ return undefined;
+};
diff --git a/client/src/reducers/encryption.js b/client/src/reducers/encryption.js
index 8fe9a2cb27f..6b04a49ac3a 100644
--- a/client/src/reducers/encryption.js
+++ b/client/src/reducers/encryption.js
@@ -62,6 +62,7 @@ const encryption = handleActions({
processingConfig: false,
processingValidate: false,
enabled: false,
+ serve_plain_dns: false,
dns_names: null,
force_https: false,
issuer: '',
diff --git a/internal/home/home.go b/internal/home/home.go
index 18d6a961ace..f0a037c0c31 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -608,7 +608,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
Context.auth, err = initUsers()
fatalOnError(err)
- Context.tls, err = newTLSManager(config.TLS)
+ Context.tls, err = newTLSManager(config.TLS, config.DNS.ServePlainDNS)
if err != nil {
log.Error("initializing tls: %s", err)
onConfigModified()
diff --git a/internal/home/tls.go b/internal/home/tls.go
index 004e9412516..e022d043ce8 100644
--- a/internal/home/tls.go
+++ b/internal/home/tls.go
@@ -38,15 +38,19 @@ type tlsManager struct {
confLock sync.Mutex
conf tlsConfigSettings
+
+ // servePlainDNS defines if plain DNS is allowed for incoming requests.
+ servePlainDNS bool
}
// newTLSManager initializes the manager of TLS configuration. m is always
// non-nil while any returned error indicates that the TLS configuration isn't
// valid. Thus TLS may be initialized later, e.g. via the web UI.
-func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
+func newTLSManager(conf tlsConfigSettings, servePlainDNS bool) (m *tlsManager, err error) {
m = &tlsManager{
- status: &tlsConfigStatus{},
- conf: conf,
+ status: &tlsConfigStatus{},
+ conf: conf,
+ servePlainDNS: servePlainDNS,
}
if m.conf.Enabled {
@@ -283,21 +287,29 @@ type tlsConfig struct {
tlsConfigSettingsExt `json:",inline"`
}
-// tlsConfigSettingsExt is used to (un)marshal the PrivateKeySaved field to
-// ensure that clients don't send and receive previously saved private keys.
+// tlsConfigSettingsExt is used to (un)marshal PrivateKeySaved field and
+// ServePlainDNS field.
type tlsConfigSettingsExt struct {
tlsConfigSettings `json:",inline"`
// PrivateKeySaved is true if the private key is saved as a string and omit
- // key from answer.
- PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
+ // key from answer. It is used to ensure that clients don't send and
+ // receive previously saved private keys.
+ PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
+
+ // ServePlainDNS defines if plain DNS is allowed for incoming requests. It
+ // is an [aghalg.NullBool] to be able to tell when it's set without using
+ // pointers.
+ ServePlainDNS aghalg.NullBool `yaml:"-" json:"serve_plain_dns"`
}
+// handleTLSStatus is the handler for the GET /control/tls/status HTTP API.
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
m.confLock.Lock()
data := tlsConfig{
tlsConfigSettingsExt: tlsConfigSettingsExt{
tlsConfigSettings: m.conf,
+ ServePlainDNS: aghalg.BoolToNullBool(m.servePlainDNS),
},
tlsConfigStatus: m.status,
}
@@ -306,6 +318,7 @@ func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
marshalTLS(w, r, data)
}
+// handleTLSValidate is the handler for the POST /control/tls/validate HTTP API.
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
if err != nil {
@@ -318,30 +331,8 @@ func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts.PrivateKey = m.conf.PrivateKey
}
- if setts.Enabled {
- err = validatePorts(
- tcpPort(config.HTTPConfig.Address.Port()),
- tcpPort(setts.PortHTTPS),
- tcpPort(setts.PortDNSOverTLS),
- tcpPort(setts.PortDNSCrypt),
- udpPort(config.DNS.Port),
- udpPort(setts.PortDNSOverQUIC),
- )
- if err != nil {
- aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
-
- return
- }
- }
-
- if !webCheckPortAvailable(setts.PortHTTPS) {
- aghhttp.Error(
- r,
- w,
- http.StatusBadRequest,
- "port %d is not available, cannot enable HTTPS on it",
- setts.PortHTTPS,
- )
+ if err = validateTLSSettings(setts); err != nil {
+ aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -358,7 +349,12 @@ func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
marshalTLS(w, r, resp)
}
-func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatus) (restartHTTPS bool) {
+// setConfig updates manager conf with the given one.
+func (m *tlsManager) setConfig(
+ newConf tlsConfigSettings,
+ status *tlsConfigStatus,
+ servePlain aghalg.NullBool,
+) (restartHTTPS bool) {
m.confLock.Lock()
defer m.confLock.Unlock()
@@ -390,9 +386,15 @@ func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatu
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.status = status
+ if servePlain != aghalg.NBNull {
+ m.servePlainDNS = servePlain == aghalg.NBTrue
+ }
+
return restartHTTPS
}
+// handleTLSConfigure is the handler for the POST /control/tls/configure HTTP
+// API.
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
@@ -405,31 +407,8 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
req.PrivateKey = m.conf.PrivateKey
}
- if req.Enabled {
- err = validatePorts(
- tcpPort(config.HTTPConfig.Address.Port()),
- tcpPort(req.PortHTTPS),
- tcpPort(req.PortDNSOverTLS),
- tcpPort(req.PortDNSCrypt),
- udpPort(config.DNS.Port),
- udpPort(req.PortDNSOverQUIC),
- )
- if err != nil {
- aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
-
- return
- }
- }
-
- // TODO(e.burkov): Investigate and perhaps check other ports.
- if !webCheckPortAvailable(req.PortHTTPS) {
- aghhttp.Error(
- r,
- w,
- http.StatusBadRequest,
- "port %d is not available, cannot enable https on it",
- req.PortHTTPS,
- )
+ if err = validateTLSSettings(req); err != nil {
+ aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -447,8 +426,18 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
return
}
- restartHTTPS := m.setConfig(req.tlsConfigSettings, status)
+ restartHTTPS := m.setConfig(req.tlsConfigSettings, status, req.ServePlainDNS)
m.setCertFileTime()
+
+ if req.ServePlainDNS != aghalg.NBNull {
+ func() {
+ m.confLock.Lock()
+ defer m.confLock.Unlock()
+
+ config.DNS.ServePlainDNS = req.ServePlainDNS == aghalg.NBTrue
+ }()
+ }
+
onConfigModified()
err = reconfigureDNSServer()
@@ -479,6 +468,33 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
}
}
+// validateTLSSettings returns error if the setts are not valid.
+func validateTLSSettings(setts tlsConfigSettingsExt) (err error) {
+ if setts.Enabled {
+ err = validatePorts(
+ tcpPort(config.HTTPConfig.Address.Port()),
+ tcpPort(setts.PortHTTPS),
+ tcpPort(setts.PortDNSOverTLS),
+ tcpPort(setts.PortDNSCrypt),
+ udpPort(config.DNS.Port),
+ udpPort(setts.PortDNSOverQUIC),
+ )
+ if err != nil {
+ // Don't wrap the error since it's informative enough as is.
+ return err
+ }
+ } else if setts.ServePlainDNS == aghalg.NBFalse {
+ // TODO(a.garipov): Support full disabling of all DNS.
+ return errors.Error("plain DNS is required in case encryption protocols are disabled")
+ }
+
+ if !webCheckPortAvailable(setts.PortHTTPS) {
+ return fmt.Errorf("port %d is not available, cannot enable HTTPS on it", setts.PortHTTPS)
+ }
+
+ return nil
+}
+
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index 16e7ab0dd42..b71ee56c8a2 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -6,6 +6,12 @@
## v0.107.42: API changes
+### The new field `"serve_plain_dns"` in `TlsConfig`
+
+* The new field `"serve_plain_dns"` in `POST /control/tls/configure`,
+ `POST /control/tls/validate` and `GET /control/tls/status` is true if plain
+ DNS is allowed for incoming requests.
+
### The new fields `"upstreams_cache_enabled"` and `"upstreams_cache_size"` in `Client` object
* The new field `"upstreams_cache_enabled"` in `GET /control/clients`,
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index c105ee1d2b2..5ab3fa528c1 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -2463,6 +2463,11 @@
'example': true
'description': >
Set to true if both certificate and private key are correct.
+ 'serve_plain_dns':
+ 'type': 'boolean'
+ 'example': true
+ 'description': >
+ Set to true if plain DNS is allowed for incoming requests.
'NetInterface':
'type': 'object'
'description': 'Network interface info'