sources: enable selection options with authentication
When authentication is enabled for an NTP source, unauthenticated NTP sources need to be disabled or limited in selection. That might be difficult to do when the configuration comes from different sources (e.g. networking scripts adding servers from DHCP). Define four modes for the source selection to consider authentication: require, prefer, mix, ignore. In different modes different selection options (require, trust, noselect) are added to authenticated and unauthenticated sources. The mode can be selected by the authselectmode directive. The mix mode is the default. The ignore mode enables the old behavior, where all sources are used exactly as specified in the configuration.
This commit is contained in:
parent
dfe877144a
commit
bddb3b3228
7 changed files with 278 additions and 9 deletions
29
conf.c
29
conf.c
|
@ -51,6 +51,7 @@ static int parse_double(char *line, double *result);
|
|||
static int parse_null(char *line);
|
||||
|
||||
static void parse_allow_deny(char *line, ARR_Instance restrictions, int allow);
|
||||
static void parse_authselectmode(char *);
|
||||
static void parse_bindacqaddress(char *);
|
||||
static void parse_bindaddress(char *);
|
||||
static void parse_bindcmdaddress(char *);
|
||||
|
@ -89,6 +90,7 @@ static double max_clock_error = 1.0; /* in ppm */
|
|||
static double max_drift = 500000.0; /* in ppm */
|
||||
static double max_slew_rate = 1e6 / 12.0; /* in ppm */
|
||||
|
||||
static SRC_AuthSelectMode authselect_mode = SRC_AUTHSELECT_MIX;
|
||||
static double max_distance = 3.0;
|
||||
static double max_jitter = 1.0;
|
||||
static double reselect_distance = 1e-4;
|
||||
|
@ -461,6 +463,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
|
|||
parse_int(p, &acquisition_port);
|
||||
} else if (!strcasecmp(command, "allow")) {
|
||||
parse_allow_deny(p, ntp_restrictions, 1);
|
||||
} else if (!strcasecmp(command, "authselectmode")) {
|
||||
parse_authselectmode(p);
|
||||
} else if (!strcasecmp(command, "bindacqaddress")) {
|
||||
parse_bindacqaddress(p);
|
||||
} else if (!strcasecmp(command, "bindaddress")) {
|
||||
|
@ -1141,6 +1145,23 @@ parse_allow_deny(char *line, ARR_Instance restrictions, int allow)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
parse_authselectmode(char *line)
|
||||
{
|
||||
if (!strcasecmp(line, "require"))
|
||||
authselect_mode = SRC_AUTHSELECT_REQUIRE;
|
||||
else if (!strcasecmp(line, "prefer"))
|
||||
authselect_mode = SRC_AUTHSELECT_PREFER;
|
||||
else if (!strcasecmp(line, "mix"))
|
||||
authselect_mode = SRC_AUTHSELECT_MIX;
|
||||
else if (!strcasecmp(line, "ignore"))
|
||||
authselect_mode = SRC_AUTHSELECT_IGNORE;
|
||||
else
|
||||
command_parse_error();
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
parse_bindacqaddress(char *line)
|
||||
{
|
||||
|
@ -1680,6 +1701,14 @@ CNF_GetCorrectionTimeRatio(void)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
SRC_AuthSelectMode
|
||||
CNF_GetAuthSelectMode(void)
|
||||
{
|
||||
return authselect_mode;
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
double
|
||||
CNF_GetMaxSlewRate(void)
|
||||
{
|
||||
|
|
2
conf.h
2
conf.h
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include "addressing.h"
|
||||
#include "reference.h"
|
||||
#include "sources.h"
|
||||
|
||||
extern void CNF_Initialise(int restarted, int client_only);
|
||||
extern void CNF_Finalise(void);
|
||||
|
@ -87,6 +88,7 @@ extern double CNF_GetMaxDrift(void);
|
|||
extern double CNF_GetCorrectionTimeRatio(void);
|
||||
extern double CNF_GetMaxSlewRate(void);
|
||||
|
||||
extern SRC_AuthSelectMode CNF_GetAuthSelectMode(void);
|
||||
extern double CNF_GetMaxDistance(void);
|
||||
extern double CNF_GetMaxJitter(void);
|
||||
extern double CNF_GetReselectDistance(void);
|
||||
|
|
|
@ -739,6 +739,74 @@ with correct time.
|
|||
|
||||
=== Source selection
|
||||
|
||||
[[authselectmode]]*authselectmode* _mode_::
|
||||
NTP sources can be specified with the *key* or *nts* option to enable
|
||||
authentication to limit the impact of man-in-the-middle attacks. The
|
||||
attackers can drop or delay NTP packets (up to the *maxdelay* and
|
||||
<<maxdistance,*maxdistance*>> limits), but they cannot modify the timestamps
|
||||
contained in the packets. The attack can cause only a limited slew or step, and
|
||||
also cause the clock to run faster or slower than real time (up to double of
|
||||
the <<maxdrift,*maxdrift*>> limit).
|
||||
+
|
||||
When authentication is enabled for an NTP source, it is important to disable
|
||||
unauthenticated NTP sources which could be exploited in the attack, e.g. if
|
||||
they are not reachable only over a trusted network. Alternatively, the source
|
||||
selection can be configured with the *require* and *trust* options to
|
||||
synchronise to the unauthenticated sources only if they agree with the
|
||||
authenticated sources and might have a positive impact on the accuracy of the
|
||||
clock. Note that in this case the impact of the attack is higher. The attackers
|
||||
cannot cause an arbitrarily large step or slew, but they have more control over
|
||||
the frequency of the clock and can cause *chronyd* to report false information,
|
||||
e.g. a significantly smaller root delay and dispersion.
|
||||
+
|
||||
This directive determines the default selection options for authenticated and
|
||||
unauthenticated sources in order to simplify the configuration with the
|
||||
configuration file and *chronyc* commands. It sets a policy for authentication.
|
||||
+
|
||||
There are four modes:
|
||||
+
|
||||
*require*:::
|
||||
Authentication is strictly required for NTP sources in this mode. If any
|
||||
unauthenticated NTP sources are specified, they will automatically get the
|
||||
*noselect* option to prevent them from being selected for synchronisation.
|
||||
*prefer*:::
|
||||
In this mode, authentication is optional and preferred. If it is enabled for at
|
||||
least one NTP source, all unauthenticated NTP sources will get the *noselect*
|
||||
option.
|
||||
*mix*:::
|
||||
In this mode, authentication is optional and synchronisation to a mix of
|
||||
authenticated and unauthenticated NTP sources is allowed. If both authenticated
|
||||
and unauthenticated NTP sources are specified, all authenticated NTP sources
|
||||
and reference clocks will get the *require* and *trust* options to prevent
|
||||
synchronisation to unauthenticated NTP sources if they do not agree with a
|
||||
majority of the authenticated sources and reference clocks. This is the default
|
||||
mode.
|
||||
*ignore*:::
|
||||
In this mode, authentication is ignored in the source selection. All sources
|
||||
will have only the selection options that were specified in the configuration
|
||||
file, or *chronyc* command. This was the behaviour of *chronyd* in versions
|
||||
before 4.0.
|
||||
::
|
||||
+
|
||||
As an example, the following configuration using the default *mix* mode:
|
||||
+
|
||||
----
|
||||
server foo.example.net nts
|
||||
server bar.example.net nts
|
||||
server baz.example.net
|
||||
refclock SHM 0
|
||||
----
|
||||
+
|
||||
is equivalent to the following configuration using the *ignore* mode:
|
||||
+
|
||||
----
|
||||
authselectmode ignore
|
||||
server foo.example.net nts require trust
|
||||
server bar.example.net nts require trust
|
||||
server baz.example.net
|
||||
refclock SHM 0 require trust
|
||||
----
|
||||
|
||||
[[combinelimit]]*combinelimit* _limit_::
|
||||
When *chronyd* has multiple sources available for synchronisation, it has to
|
||||
select one source as the synchronisation source. The measured offsets and
|
||||
|
|
56
sources.c
56
sources.c
|
@ -495,10 +495,62 @@ SRC_ResetReachability(SRC_Instance inst)
|
|||
static void
|
||||
update_sel_options(void)
|
||||
{
|
||||
int i;
|
||||
int options, auth_ntp_options, unauth_ntp_options, refclk_options;
|
||||
int i, auth_ntp_sources, unauth_ntp_sources;
|
||||
|
||||
auth_ntp_sources = unauth_ntp_sources = 0;
|
||||
|
||||
for (i = 0; i < n_sources; i++) {
|
||||
sources[i]->sel_options = sources[i]->conf_sel_options;
|
||||
if (sources[i]->type != SRC_NTP)
|
||||
continue;
|
||||
if (sources[i]->authenticated)
|
||||
auth_ntp_sources++;
|
||||
else
|
||||
unauth_ntp_sources++;
|
||||
}
|
||||
|
||||
auth_ntp_options = unauth_ntp_options = refclk_options = 0;
|
||||
|
||||
/* Determine which selection options need to be added to authenticated NTP
|
||||
sources, unauthenticated NTP sources, and refclocks, to follow the
|
||||
configured selection mode */
|
||||
switch (CNF_GetAuthSelectMode()) {
|
||||
case SRC_AUTHSELECT_IGNORE:
|
||||
break;
|
||||
case SRC_AUTHSELECT_MIX:
|
||||
if (auth_ntp_sources > 0 && unauth_ntp_sources > 0)
|
||||
auth_ntp_options = refclk_options = SRC_SELECT_REQUIRE | SRC_SELECT_TRUST;
|
||||
break;
|
||||
case SRC_AUTHSELECT_PREFER:
|
||||
if (auth_ntp_sources > 0)
|
||||
unauth_ntp_options = SRC_SELECT_NOSELECT;
|
||||
break;
|
||||
case SRC_AUTHSELECT_REQUIRE:
|
||||
unauth_ntp_options = SRC_SELECT_NOSELECT;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
for (i = 0; i < n_sources; i++) {
|
||||
options = sources[i]->conf_sel_options;
|
||||
|
||||
switch (sources[i]->type) {
|
||||
case SRC_NTP:
|
||||
options |= sources[i]->authenticated ? auth_ntp_options : unauth_ntp_options;
|
||||
break;
|
||||
case SRC_REFCLOCK:
|
||||
options |= refclk_options;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (sources[i]->sel_options != options) {
|
||||
DEBUG_LOG("changing %s from %x to %x", source_to_string(sources[i]),
|
||||
(unsigned int)sources[i]->sel_options, (unsigned int)options);
|
||||
sources[i]->sel_options = options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,14 @@ extern void SRC_Initialise(void);
|
|||
/* Finalisation function */
|
||||
extern void SRC_Finalise(void);
|
||||
|
||||
/* Modes for selecting NTP sources based on their authentication status */
|
||||
typedef enum {
|
||||
SRC_AUTHSELECT_IGNORE,
|
||||
SRC_AUTHSELECT_MIX,
|
||||
SRC_AUTHSELECT_PREFER,
|
||||
SRC_AUTHSELECT_REQUIRE,
|
||||
} SRC_AuthSelectMode;
|
||||
|
||||
typedef enum {
|
||||
SRC_NTP, /* NTP client/peer */
|
||||
SRC_REFCLOCK /* Rerefence clock */
|
||||
|
|
|
@ -65,4 +65,28 @@ check_packet_interval || test_fail
|
|||
check_source_selection && test_fail
|
||||
check_sync && test_fail
|
||||
|
||||
cat > tmp/keys <<-EOF
|
||||
1 MD5 HEX:1B81CBF88D4A73F2E8CE59647F6E5C1719B6CAF5
|
||||
EOF
|
||||
|
||||
server_conf="keyfile tmp/keys"
|
||||
client_server_conf="
|
||||
server 192.168.123.1 key 1
|
||||
server 192.168.123.2
|
||||
server 192.168.123.3"
|
||||
|
||||
for authselectmode in require prefer mix ignore; do
|
||||
client_conf="keyfile tmp/keys
|
||||
authselectmode $authselectmode"
|
||||
run_test || test_fail
|
||||
check_chronyd_exit || test_fail
|
||||
check_source_selection || test_fail
|
||||
check_packet_interval || test_fail
|
||||
if [ $authselectmode = ignore ]; then
|
||||
check_sync || test_fail
|
||||
else
|
||||
check_sync && test_fail
|
||||
fi
|
||||
done
|
||||
|
||||
test_pass
|
||||
|
|
|
@ -21,14 +21,27 @@
|
|||
#include <sources.c>
|
||||
#include "test.h"
|
||||
|
||||
static SRC_Instance
|
||||
create_source(SRC_Type type, int authenticated, int sel_options)
|
||||
{
|
||||
static IPAddr addr;
|
||||
|
||||
TST_GetRandomAddress(&addr, IPADDR_UNSPEC, -1);
|
||||
|
||||
return SRC_CreateNewInstance(UTI_IPToRefid(&addr), type, authenticated, sel_options,
|
||||
type == SRC_NTP ? &addr : NULL,
|
||||
SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void
|
||||
test_unit(void)
|
||||
{
|
||||
SRC_AuthSelectMode sel_mode;
|
||||
SRC_Instance srcs[16];
|
||||
RPT_SourceReport report;
|
||||
NTP_Sample sample;
|
||||
IPAddr addr;
|
||||
int i, j, k, l, samples, sel_options;
|
||||
int i, j, k, l, n1, n2, n3, samples, sel_options;
|
||||
char conf[128];
|
||||
|
||||
CNF_Initialise(0, 0);
|
||||
LCL_Initialise();
|
||||
|
@ -45,15 +58,11 @@ test_unit(void)
|
|||
for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
|
||||
TEST_CHECK(n_sources == j);
|
||||
|
||||
TST_GetRandomAddress(&addr, IPADDR_UNSPEC, -1);
|
||||
|
||||
sel_options = i & random() & (SRC_SELECT_NOSELECT | SRC_SELECT_PREFER |
|
||||
SRC_SELECT_TRUST | SRC_SELECT_REQUIRE);
|
||||
|
||||
DEBUG_LOG("added source %d options %d", j, sel_options);
|
||||
srcs[j] = SRC_CreateNewInstance(UTI_IPToRefid(&addr), SRC_NTP, 0, sel_options, &addr,
|
||||
SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES,
|
||||
0.0, 1.0);
|
||||
srcs[j] = create_source(SRC_NTP, 0, sel_options);
|
||||
SRC_UpdateReachability(srcs[j], 1);
|
||||
|
||||
samples = (i + j) % 5 + 3;
|
||||
|
@ -132,6 +141,83 @@ test_unit(void)
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CHECK(CNF_GetAuthSelectMode() == SRC_AUTHSELECT_MIX);
|
||||
|
||||
for (i = 0; i < 1000; i++) {
|
||||
DEBUG_LOG("iteration %d", i);
|
||||
|
||||
switch (i % 4) {
|
||||
case 0:
|
||||
snprintf(conf, sizeof (conf), "authselectmode require");
|
||||
sel_mode = SRC_AUTHSELECT_REQUIRE;
|
||||
break;
|
||||
case 1:
|
||||
snprintf(conf, sizeof (conf), "authselectmode prefer");
|
||||
sel_mode = SRC_AUTHSELECT_PREFER;
|
||||
break;
|
||||
case 2:
|
||||
snprintf(conf, sizeof (conf), "authselectmode mix");
|
||||
sel_mode = SRC_AUTHSELECT_MIX;
|
||||
break;
|
||||
case 3:
|
||||
snprintf(conf, sizeof (conf), "authselectmode ignore");
|
||||
sel_mode = SRC_AUTHSELECT_IGNORE;
|
||||
break;
|
||||
}
|
||||
|
||||
CNF_ParseLine(NULL, 0, conf);
|
||||
TEST_CHECK(CNF_GetAuthSelectMode() == sel_mode);
|
||||
|
||||
sel_options = random() & (SRC_SELECT_NOSELECT | SRC_SELECT_PREFER |
|
||||
SRC_SELECT_TRUST | SRC_SELECT_REQUIRE);
|
||||
|
||||
n1 = random() % 3;
|
||||
n2 = random() % 3;
|
||||
n3 = random() % 3;
|
||||
assert(n1 + n2 + n3 < sizeof (srcs) / sizeof (srcs[0]));
|
||||
|
||||
for (j = 0; j < n1; j++)
|
||||
srcs[j] = create_source(SRC_REFCLOCK, random() % 2, sel_options);
|
||||
for (; j < n1 + n2; j++)
|
||||
srcs[j] = create_source(SRC_NTP, 1, sel_options);
|
||||
for (; j < n1 + n2 + n3; j++)
|
||||
srcs[j] = create_source(SRC_NTP, 0, sel_options);
|
||||
|
||||
switch (sel_mode) {
|
||||
case SRC_AUTHSELECT_IGNORE:
|
||||
for (j = 0; j < n1 + n2 + n3; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options == sel_options);
|
||||
break;
|
||||
case SRC_AUTHSELECT_MIX:
|
||||
for (j = 0; j < n1 + n2; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options ==
|
||||
(sel_options | (n2 > 0 && n3 > 0 ? SRC_SELECT_REQUIRE | SRC_SELECT_TRUST : 0)));
|
||||
for (; j < n1 + n2 + n3; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options == sel_options);
|
||||
break;
|
||||
case SRC_AUTHSELECT_PREFER:
|
||||
for (j = 0; j < n1 + n2; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options == sel_options);
|
||||
for (; j < n1 + n2 + n3; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options == (sel_options | (n2 > 0 ? SRC_SELECT_NOSELECT : 0)));
|
||||
break;
|
||||
case SRC_AUTHSELECT_REQUIRE:
|
||||
for (j = 0; j < n1 + n2; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options == sel_options);
|
||||
for (; j < n1 + n2 + n3; j++)
|
||||
TEST_CHECK(srcs[j]->sel_options == (sel_options | SRC_SELECT_NOSELECT));
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
for (j = n1 + n2 + n3 - 1; j >= 0; j--) {
|
||||
if (j < n1 + n2)
|
||||
TEST_CHECK(srcs[j]->sel_options == sel_options);
|
||||
SRC_DestroyInstance(srcs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
REF_Finalise();
|
||||
SRC_Finalise();
|
||||
SCH_Finalise();
|
||||
|
|
Loading…
Reference in a new issue