Create UI for DNS rebinding protection
This commit is contained in:
@@ -587,5 +587,11 @@
|
|||||||
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
|
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
|
||||||
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
|
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
|
||||||
"experimental": "Experimental"
|
"experimental": "Experimental",
|
||||||
|
"rebinding_title": "DNS Rebinding Protection",
|
||||||
|
"rebinding_desc": "Here you can configure protection against DNS rebinding attacks",
|
||||||
|
"rebinding_protection_enabled": "Enable protection from DNS rebinding attacks",
|
||||||
|
"rebinding_protection_enabled_desc": "If enabled, AdGuard Home will block responses containing host on the local network.",
|
||||||
|
"rebinding_allowed_hosts_title": "Allowed domains",
|
||||||
|
"rebinding_allowed_hosts_desc": "A list of domains. If configured, AdGuard Home will allow responses containing host on the local network from these domains."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ export const setDnsConfig = (config) => async (dispatch) => {
|
|||||||
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
||||||
hasDnsSettings = true;
|
hasDnsSettings = true;
|
||||||
}
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, 'rebinding_allowed_hosts')) {
|
||||||
|
data.rebinding_allowed_hosts = splitByNewLine(config.rebinding_allowed_hosts);
|
||||||
|
}
|
||||||
|
|
||||||
await apiClient.setDnsConfig(data);
|
await apiClient.setDnsConfig(data);
|
||||||
|
|
||||||
|
|||||||
91
client/src/components/Settings/Dns/Rebinding/Form.js
Normal file
91
client/src/components/Settings/Dns/Rebinding/Form.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Field, reduxForm } from 'redux-form';
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
import { renderTextareaField, CheckboxField } from '../../../../helpers/form';
|
||||||
|
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||||
|
import { FORM_NAME } from '../../../../helpers/constants';
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
id: 'rebinding_allowed_hosts',
|
||||||
|
title: 'rebinding_allowed_hosts_title',
|
||||||
|
subtitle: 'rebinding_allowed_hosts_desc',
|
||||||
|
normalizeOnBlur: removeEmptyLines,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const Form = ({
|
||||||
|
handleSubmit, submitting, invalid,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { processingSetConfig } = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||||
|
|
||||||
|
const renderField = ({
|
||||||
|
id, title, subtitle, disabled = processingSetConfig, normalizeOnBlur,
|
||||||
|
}) => <div key={id} className="form__group mb-5">
|
||||||
|
<label className="form__label form__label--with-desc" htmlFor={id}>
|
||||||
|
<Trans>{title}</Trans>
|
||||||
|
</label>
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
<Trans>{subtitle}</Trans>
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
id={id}
|
||||||
|
name={id}
|
||||||
|
component={renderTextareaField}
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control--textarea font-monospace"
|
||||||
|
disabled={disabled}
|
||||||
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
renderField.propTypes = {
|
||||||
|
id: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
normalizeOnBlur: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name={'rebinding_protection_enabled'}
|
||||||
|
type="checkbox"
|
||||||
|
component={CheckboxField}
|
||||||
|
placeholder={t('rebinding_protection_enabled')}
|
||||||
|
subtitle={t('rebinding_protection_enabled_desc')}
|
||||||
|
disabled={processingSetConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{fields.map(renderField)}
|
||||||
|
|
||||||
|
<div className="card-actions">
|
||||||
|
<div className="btn-list">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success btn-standard"
|
||||||
|
disabled={submitting || invalid || processingSetConfig}
|
||||||
|
>
|
||||||
|
<Trans>save_config</Trans>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.propTypes = {
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
submitting: PropTypes.bool.isRequired,
|
||||||
|
invalid: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reduxForm({ form: FORM_NAME.REBINDING })(Form);
|
||||||
36
client/src/components/Settings/Dns/Rebinding/index.js
Normal file
36
client/src/components/Settings/Dns/Rebinding/index.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import Form from './Form';
|
||||||
|
import Card from '../../../ui/Card';
|
||||||
|
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||||
|
|
||||||
|
const RebindingConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
rebinding_protection_enabled, rebinding_allowed_hosts,
|
||||||
|
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||||
|
|
||||||
|
const handleFormSubmit = (values) => {
|
||||||
|
dispatch(setDnsConfig(values));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={t('rebinding_title')}
|
||||||
|
subtitle={t('rebinding_desc')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
initialValues={{
|
||||||
|
rebinding_protection_enabled,
|
||||||
|
rebinding_allowed_hosts,
|
||||||
|
}}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RebindingConfig;
|
||||||
@@ -8,6 +8,7 @@ import Config from './Config';
|
|||||||
import PageTitle from '../../ui/PageTitle';
|
import PageTitle from '../../ui/PageTitle';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import CacheConfig from './Cache';
|
import CacheConfig from './Cache';
|
||||||
|
import RebindingConfig from './Rebinding';
|
||||||
import { getDnsConfig } from '../../../actions/dnsConfig';
|
import { getDnsConfig } from '../../../actions/dnsConfig';
|
||||||
import { getAccessList } from '../../../actions/access';
|
import { getAccessList } from '../../../actions/access';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const Dns = () => {
|
|||||||
<Config />
|
<Config />
|
||||||
<CacheConfig />
|
<CacheConfig />
|
||||||
<Access />
|
<Access />
|
||||||
|
<RebindingConfig />
|
||||||
</>}
|
</>}
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -509,6 +509,7 @@ export const FORM_NAME = {
|
|||||||
INSTALL: 'install',
|
INSTALL: 'install',
|
||||||
LOGIN: 'login',
|
LOGIN: 'login',
|
||||||
CACHE: 'cache',
|
CACHE: 'cache',
|
||||||
|
REBINDING: 'rebinding',
|
||||||
...DHCP_FORM_NAMES,
|
...DHCP_FORM_NAMES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const dnsConfig = handleActions(
|
|||||||
blocking_ipv6,
|
blocking_ipv6,
|
||||||
upstream_dns,
|
upstream_dns,
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
|
rebinding_allowed_hosts,
|
||||||
...values
|
...values
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ const dnsConfig = handleActions(
|
|||||||
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
||||||
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
|
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
|
||||||
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
||||||
|
rebinding_allowed_hosts: (rebinding_allowed_hosts && rebinding_allowed_hosts.join('\n')) || '',
|
||||||
processingGetConfig: false,
|
processingGetConfig: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user