Skip to content

Commit

Permalink
Create UI for DNS rebinding protection
Browse files Browse the repository at this point in the history
  • Loading branch information
juniorz committed Jan 3, 2021
1 parent be8ac6c commit 3a4ff66
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 8 deletions.
10 changes: 9 additions & 1 deletion client/src/__locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -586,5 +586,13 @@
"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.",
"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. Here you can specify the exact domain names, wildcards and urlfilter-rules, e.g. 'example.org', '*.example.org' or '||example.org^'.",
"rebinding_applied": "DNS rebinding protection applied",
"blocked_rebind": "Blocked rebinding"
}
3 changes: 3 additions & 0 deletions client/src/actions/dnsConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export const setDnsConfig = (config) => async (dispatch) => {
data.upstream_dns = splitByNewLine(config.upstream_dns);
hasDnsSettings = true;
}
if (Object.prototype.hasOwnProperty.call(data, 'rebinding_allowed_hosts')) {
data.rebinding_allowed_hosts = splitByNewLine(config.rebinding_allowed_hosts);
}

await apiClient.setDnsConfig(data);

Expand Down
1 change: 1 addition & 0 deletions client/src/components/Filters/Check/Info.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const getTitle = () => {
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: getReasonFiltered(reason),
[FILTERED_STATUS.FILTERED_SAFE_BROWSING]: getReasonFiltered(reason),
[FILTERED_STATUS.FILTERED_PARENTAL]: getReasonFiltered(reason),
[FILTERED_STATUS.FILTERED_BLOCKED_REBIND]: t('rebinding_applied'),
};

if (Object.prototype.hasOwnProperty.call(REASON_TO_TITLE_MAP, reason)) {
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/Logs/Cells/ClientCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { nanoid } from 'nanoid';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import propTypes from 'prop-types';
import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers';
import { checkFiltered, checkBlockedRebind, getBlockingClientName } from '../../../helpers/helpers';
import { BLOCK_ACTIONS } from '../../../helpers/constants';
import { toggleBlocking, toggleBlockingForClient } from '../../../actions';
import IconTooltip from './IconTooltip';
Expand Down Expand Up @@ -48,6 +48,7 @@ const ClientCell = ({
const processedData = Object.entries(data);

const isFiltered = checkFiltered(reason);
const isBlockedRebinding = checkBlockedRebind(reason);

const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
'mt-2': isDetailed && !name && !whoisAvailable,
Expand Down Expand Up @@ -125,7 +126,7 @@ const ClientCell = ({
'button-action__container--detailed': isDetailed,
});

return <div className={containerClass}>
return isBlockedRebinding || <div className={containerClass}>
<button type="button"
className={buttonClass}
onClick={onClick}
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Logs/Cells/ResponseCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const ResponseCell = ({
return formattedElapsedMs;
}
return getServiceName(service_name);
// case FILTERED_STATUS.FILTERED_BLOCKED_REBIND: // TODO??
case FILTERED_STATUS.FILTERED_BLACK_LIST:
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
return getFilterNames(rules, filters, whitelistFilters).join(', ');
Expand Down
10 changes: 7 additions & 3 deletions client/src/components/Logs/Cells/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import propTypes from 'prop-types';
import {
captitalizeWords,
checkFiltered,
checkBlockedRebind,
getRulesToFilterList,
formatDateTime,
formatElapsedMs,
Expand Down Expand Up @@ -90,6 +91,7 @@ const Row = memo(({

const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
const isFiltered = checkFiltered(reason);
const isBlockedRebinding = checkBlockedRebind(reason);

const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
Expand Down Expand Up @@ -183,9 +185,11 @@ const Row = memo(({
source_label: source,
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
original_response: originalResponse?.join('\n'),
[BUTTON_PREFIX + buttonType]: blockButton,
[BUTTON_PREFIX + blockingForClientKey]: blockForClientButton,
[BUTTON_PREFIX + blockingClientKey]: blockClientButton,
...(isBlockedRebinding || {
[BUTTON_PREFIX + buttonType]: blockButton,
[BUTTON_PREFIX + blockingForClientKey]: blockForClientButton,
[BUTTON_PREFIX + blockingClientKey]: blockClientButton,
}),
};

setDetailedDataCurrent(processContent(detailedData));
Expand Down
91 changes: 91 additions & 0 deletions client/src/components/Settings/Dns/Rebinding/Form.js
Original file line number Diff line number Diff line change
@@ -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 changes: 36 additions & 0 deletions client/src/components/Settings/Dns/Rebinding/index.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions client/src/components/Settings/Dns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Config from './Config';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import CacheConfig from './Cache';
import RebindingConfig from './Rebinding';
import { getDnsConfig } from '../../../actions/dnsConfig';
import { getAccessList } from '../../../actions/access';

Expand All @@ -33,6 +34,7 @@ const Dns = () => {
<Config />
<CacheConfig />
<Access />
<RebindingConfig />
</>}
</>;
};
Expand Down
10 changes: 10 additions & 0 deletions client/src/helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ export const FILTERED_STATUS = {
FILTERED_BLACK_LIST: 'FilteredBlackList',
NOT_FILTERED_WHITE_LIST: 'NotFilteredWhiteList',
NOT_FILTERED_NOT_FOUND: 'NotFilteredNotFound',
FILTERED_BLOCKED_REBIND: 'FilteredRebind',
FILTERED_BLOCKED_SERVICE: 'FilteredBlockedService',
REWRITE: 'Rewrite',
REWRITE_HOSTS: 'RewriteEtcHosts',
Expand All @@ -362,6 +363,10 @@ export const RESPONSE_FILTER = {
QUERY: 'blocked',
LABEL: 'show_blocked_responses',
},
BLOCKED_REBIND: {
QUERY: 'blocked_rebind',
LABEL: 'blocked_rebind',
},
BLOCKED_SERVICES: {
QUERY: 'blocked_services',
LABEL: 'blocked_services',
Expand Down Expand Up @@ -443,6 +448,10 @@ export const FILTERED_STATUS_TO_META_MAP = {
LABEL: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.LABEL,
COLOR: QUERY_STATUS_COLORS.YELLOW,
},
[FILTERED_STATUS.FILTERED_BLOCKED_REBIND]: {
LABEL: RESPONSE_FILTER.BLOCKED_REBIND.LABEL,
COLOR: QUERY_STATUS_COLORS.RED,
},
};

export const DEFAULT_TIME_FORMAT = 'HH:mm:ss';
Expand Down Expand Up @@ -514,6 +523,7 @@ export const FORM_NAME = {
INSTALL: 'install',
LOGIN: 'login',
CACHE: 'cache',
REBINDING: 'rebinding',
...DHCP_FORM_NAMES,
};

Expand Down
1 change: 1 addition & 0 deletions client/src/helpers/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ export const checkNotFilteredNotFound = (reason) => reason === FILTERED_STATUS.N
export const checkSafeSearch = (reason) => reason === FILTERED_STATUS.FILTERED_SAFE_SEARCH;
export const checkSafeBrowsing = (reason) => reason === FILTERED_STATUS.FILTERED_SAFE_BROWSING;
export const checkParental = (reason) => reason === FILTERED_STATUS.FILTERED_PARENTAL;
export const checkBlockedRebind = (reason) => reason === FILTERED_STATUS.FILTERED_BLOCKED_REBIND;
export const checkBlockedService = (reason) => reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;

export const getCurrentFilter = (url, filters) => {
Expand Down
2 changes: 2 additions & 0 deletions client/src/reducers/dnsConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const dnsConfig = handleActions(
blocking_ipv6,
upstream_dns,
bootstrap_dns,
rebinding_allowed_hosts,
...values
} = payload;

Expand All @@ -26,6 +27,7 @@ const dnsConfig = handleActions(
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
rebinding_allowed_hosts: (rebinding_allowed_hosts && rebinding_allowed_hosts.join('\n')) || '',
processingGetConfig: false,
};
},
Expand Down
8 changes: 6 additions & 2 deletions internal/querylog/searchcriteria.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
filteringStatusFiltered = "filtered" // all kinds of filtering

filteringStatusBlocked = "blocked" // blocked or blocked services
filteringStatusBlockedRebind = "blocked_rebind" // blocked due to rebind attempt
filteringStatusBlockedService = "blocked_services" // blocked
filteringStatusBlockedSafebrowsing = "blocked_safebrowsing" // blocked by safebrowsing
filteringStatusBlockedParental = "blocked_parental" // blocked by parental control
Expand All @@ -29,7 +30,7 @@ const (

// filteringStatusValues -- array with all possible filteringStatus values
var filteringStatusValues = []string{
filteringStatusAll, filteringStatusFiltered, filteringStatusBlocked,
filteringStatusAll, filteringStatusFiltered, filteringStatusBlocked, filteringStatusBlockedRebind,
filteringStatusBlockedService, filteringStatusBlockedSafebrowsing, filteringStatusBlockedParental,
filteringStatusWhitelisted, filteringStatusRewritten, filteringStatusSafeSearch,
filteringStatusProcessed,
Expand Down Expand Up @@ -123,7 +124,10 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool {

case filteringStatusBlocked:
return res.IsFiltered &&
res.Reason.In(dnsfilter.FilteredBlockList, dnsfilter.FilteredBlockedService)
res.Reason.In(dnsfilter.FilteredBlockList, dnsfilter.FilteredBlockedService, dnsfilter.FilteredRebind)

case filteringStatusBlockedRebind:
return res.IsFiltered && res.Reason == dnsfilter.FilteredRebind

case filteringStatusBlockedService:
return res.IsFiltered && res.Reason == dnsfilter.FilteredBlockedService
Expand Down

0 comments on commit 3a4ff66

Please sign in to comment.