cmdmon: add authdata command

Add a command to display information about authentication of NTP
sources.
This commit is contained in:
Miroslav Lichvar 2020-05-13 16:54:55 +02:00
parent 75beeaf2b0
commit 79c7384e5e
16 changed files with 318 additions and 3 deletions

29
candm.h
View file

@ -104,7 +104,8 @@
#define REQ_ADD_SOURCE 64
#define REQ_NTP_SOURCE_NAME 65
#define REQ_RESET_SOURCES 66
#define N_REQUEST_TYPES 67
#define REQ_AUTH_DATA 67
#define N_REQUEST_TYPES 68
/* Structure used to exchange timespecs independent of time_t size */
typedef struct {
@ -351,6 +352,11 @@ typedef struct {
int32_t EOR;
} REQ_NTPSourceName;
typedef struct {
IPAddr ip_addr;
int32_t EOR;
} REQ_AuthData;
/* ================================================== */
#define PKT_TYPE_CMD_REQUEST 1
@ -454,6 +460,7 @@ typedef struct {
REQ_SmoothTime smoothtime;
REQ_NTPData ntp_data;
REQ_NTPSourceName ntp_source_name;
REQ_AuthData auth_data;
} data; /* Command specific parameters */
/* Padding used to prevent traffic amplification. It only defines the
@ -491,7 +498,8 @@ typedef struct {
#define RPY_MANUAL_TIMESTAMP2 17
#define RPY_MANUAL_LIST2 18
#define RPY_NTP_SOURCE_NAME 19
#define N_REPLY_TYPES 20
#define RPY_AUTH_DATA 20
#define N_REPLY_TYPES 21
/* Status codes */
#define STT_SUCCESS 0
@ -712,6 +720,22 @@ typedef struct {
int32_t EOR;
} RPY_NTPSourceName;
#define RPY_AD_MD_NONE 0
#define RPY_AD_MD_SYMMETRIC 1
#define RPY_AD_MD_NTS 2
typedef struct {
uint16_t mode;
uint16_t key_type;
uint32_t key_id;
uint16_t key_length;
uint16_t ke_attempts;
uint32_t last_ke_ago;
uint16_t cookies;
uint16_t nak;
int32_t EOR;
} RPY_AuthData;
typedef struct {
uint8_t version;
uint8_t pkt_type;
@ -742,6 +766,7 @@ typedef struct {
RPY_Smoothing smoothing;
RPY_NTPData ntp_data;
RPY_NTPSourceName ntp_source_name;
RPY_AuthData auth_data;
} data; /* Reply specific parameters */
} CMD_Reply;

View file

@ -1214,6 +1214,7 @@ give_help(void)
"\0\0"
"NTP sources:\0\0"
"activity\0Check how many NTP sources are online/offline\0"
"authdata [-a]\0Display information about authentication\0"
"ntpdata [<address>]\0Display information about last valid measurement\0"
"add server <name> [options]\0Add new NTP server\0"
"add pool <name> [options]\0Add new pool of NTP servers\0"
@ -1312,7 +1313,7 @@ command_name_generator(const char *text, int state)
{
const char *name, **names[TAB_COMPLETE_MAX_INDEX];
const char *base_commands[] = {
"accheck", "activity", "add", "allow", "burst",
"accheck", "activity", "add", "allow", "authdata", "burst",
"clients", "cmdaccheck", "cmdallow", "cmddeny", "cyclelogs", "delete",
"deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep",
"manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
@ -2363,6 +2364,82 @@ process_cmd_tracking(char *line)
/* ================================================== */
static int
process_cmd_authdata(char *line)
{
CMD_Request request;
CMD_Reply reply;
IPAddr ip_addr;
uint32_t i, source_mode, n_sources;
int all, verbose;
const char *mode_str;
char name[256];
parse_sources_options(line, &all, &verbose);
request.command = htons(REQ_N_SOURCES);
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
return 0;
n_sources = ntohl(reply.data.n_sources.n_sources);
print_header("Name/IP address Mode KeyID Type Len Last Atmp Cook NAK");
/* "NNNNNNNNNNNNNNNNNNNNNNNNNNN MMMM KKKKK AAAA LLLL LLLL AAAA CCCC NNNN" */
for (i = 0; i < n_sources; i++) {
request.command = htons(REQ_SOURCE_DATA);
request.data.source_data.index = htonl(i);
if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
return 0;
source_mode = ntohs(reply.data.source_data.mode);
if (source_mode != RPY_SD_MD_CLIENT && source_mode != RPY_SD_MD_PEER)
continue;
UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
if (!all && ip_addr.family == IPADDR_ID)
continue;
request.command = htons(REQ_AUTH_DATA);
request.data.auth_data.ip_addr = reply.data.source_data.ip_addr;
if (!request_reply(&request, &reply, RPY_AUTH_DATA, 0))
return 0;
format_name(name, sizeof (name), 25, 0, 0, 1, &ip_addr);
switch (ntohs(reply.data.auth_data.mode)) {
case RPY_AD_MD_NONE:
mode_str = "-";
break;
case RPY_AD_MD_SYMMETRIC:
mode_str = "SK";
break;
case RPY_AD_MD_NTS:
mode_str = "NTS";
break;
default:
mode_str = "?";
break;
}
print_report("%-27s %4s %5U %4d %4d %I %4d %4d %4d\n",
name, mode_str,
(unsigned long)ntohl(reply.data.auth_data.key_id),
ntohs(reply.data.auth_data.key_type),
ntohs(reply.data.auth_data.key_length),
(unsigned long)ntohl(reply.data.auth_data.last_ke_ago),
ntohs(reply.data.auth_data.ke_attempts),
ntohs(reply.data.auth_data.cookies),
ntohs(reply.data.auth_data.nak),
REPORT_END);
}
return 1;
}
/* ================================================== */
static int
process_cmd_ntpdata(char *line)
{
@ -3050,6 +3127,9 @@ process_line(char *line)
} else {
do_normal_submit = process_cmd_allow(&tx_message, line);
}
} else if (!strcmp(command, "authdata")) {
do_normal_submit = 0;
ret = process_cmd_authdata(line);
} else if (!strcmp(command, "burst")) {
do_normal_submit = process_cmd_burst(&tx_message, line);
} else if (!strcmp(command, "clients")) {

View file

@ -136,6 +136,7 @@ static const char permissions[] = {
PERMIT_AUTH, /* ADD_SOURCE */
PERMIT_OPEN, /* NTP_SOURCE_NAME */
PERMIT_AUTH, /* RESET_SOURCES */
PERMIT_AUTH, /* AUTH_DATA */
};
/* ================================================== */
@ -1235,6 +1236,46 @@ handle_reset_sources(CMD_Request *rx_message, CMD_Reply *tx_message)
LCL_NotifyExternalTimeStep(&now, &cooked_now, 0.0, 0.0);
}
/* ================================================== */
static void
handle_auth_data(CMD_Request *rx_message, CMD_Reply *tx_message)
{
RPT_AuthReport report;
IPAddr ip_addr;
UTI_IPNetworkToHost(&rx_message->data.auth_data.ip_addr, &ip_addr);
if (!NSR_GetAuthReport(&ip_addr, &report)) {
tx_message->status = htons(STT_NOSUCHSOURCE);
return;
}
tx_message->reply = htons(RPY_AUTH_DATA);
switch (report.mode) {
case NTP_AUTH_NONE:
tx_message->data.auth_data.mode = htons(RPY_AD_MD_NONE);
break;
case NTP_AUTH_SYMMETRIC:
tx_message->data.auth_data.mode = htons(RPY_AD_MD_SYMMETRIC);
break;
case NTP_AUTH_NTS:
tx_message->data.auth_data.mode = htons(RPY_AD_MD_NTS);
break;
default:
break;
}
tx_message->data.auth_data.key_type = htons(report.key_type);
tx_message->data.auth_data.key_id = htonl(report.key_id);
tx_message->data.auth_data.key_length = htons(report.key_length);
tx_message->data.auth_data.ke_attempts = htons(report.ke_attempts);
tx_message->data.auth_data.last_ke_ago = htonl(report.last_ke_ago);
tx_message->data.auth_data.cookies = htons(report.cookies);
tx_message->data.auth_data.nak = htons(report.nak);
}
/* ================================================== */
/* Read a packet and process it */
@ -1617,6 +1658,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
handle_reset_sources(&rx_message, &tx_message);
break;
case REQ_AUTH_DATA:
handle_auth_data(&rx_message, &tx_message);
break;
default:
DEBUG_LOG("Unhandled command %d", rx_command);
tx_message.status = htons(STT_FAILED);

View file

@ -454,6 +454,73 @@ the offline state.
the name of the server or peer was not resolved to an address yet; this source is
not visible in the *sources* and *sourcestats* reports.
[[authdata]]*authdata* [*-a*]::
The *authdata* command displays information specific to authentication of NTP
sources. If the *-a* option is specified, all sources are displayed, including
those that do not have a known address yet. An example of the output is
shown below.
+
----
Name/IP address Mode KeyID Type Len Last Atmp Cook NAK
====================================================================
foo.example.com NTS 1 15 256 135m 0 8 0
bar.example.com SK 30 13 128 - 0 0 0
baz.example.com - 0 0 0 - 0 0 0
----
+
The columns are as follows:
+
*Name/IP address*:::
This column shows the name or the IP address of the source.
*Mode*:::
This column shows which mechanism authenticates NTP packets received from the
source. _NTS_ means Network Time Security, _SK_ means a symmetric key, and _-_
means authentication is disabled.
*KeyID*:::
This column shows an identifier of the key used for authentication. With a
symmetric key, it is the ID from the <<chrony.conf.adoc#keyfile,key file>>.
With NTS, it is a number starting at zero and incremented by one with each
successful key establishment using the NTS-KE protocol, i.e. it shows how many
times the key establishment was performed with this source.
*Type*:::
This columns shows an identifier of the algorithm used for authentication.
With a symmetric key, it is the hash function or cipher specified in the key
file. With NTS, it is an authenticated encryption with associated data (AEAD)
algorithm, which is negotiated in the NTS-KE protocol. The following values can
be reported:
* 1: MD5
* 2: SHA1
* 3: SHA256
* 4: SHA384
* 5: SHA512
* 6: SHA3-224
* 7: SHA3-256
* 8: SHA3-384
* 9: SHA3-512
* 10: TIGER
* 11: WHIRLPOOL
* 13: AES128
* 14: AES256
* 15: AEAD-AES-SIV-CMAC-256
*Len*:::
This column shows the length of the key in bits.
*Last*:::
This column shows how long ago the last successful key establishment was
performed. It is in seconds, or letters _m_, _h_, _d_ or _y_ indicate minutes,
hours, days, or years.
*Atmp*:::
This column shows the number of attempts to perform the key establishment since
the last successful key establishment. A number larger than 1 indicates a
problem with the network or server.
*Cook*:::
This column shows the number of NTS cookies that *chronyd* currently has. If
the key establishment was successful, a number smaller than 8 indicates a
problem with the network or server.
*NAK*:::
This column shows whether an NTS NAK was received since the last authenticated
response. A non-zero number indicates that *chronyd* has used a cookie which is
no longer valid, or it might be under a denial-of-service attack.
[[ntpdata]]*ntpdata* [_address_]::
The *ntpdata* command displays the last valid measurement and other
NTP-specific information about the specified NTP source, or all NTP sources

View file

@ -498,3 +498,28 @@ NAU_DumpData(NAU_Instance instance)
break;
}
}
/* ================================================== */
void
NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report)
{
memset(report, 0, sizeof (*report));
report->mode = instance->mode;
report->last_ke_ago = -1;
switch (instance->mode) {
case NTP_AUTH_NONE:
break;
case NTP_AUTH_SYMMETRIC:
report->key_id = instance->key_id;
KEY_GetKeyInfo(instance->key_id, &report->key_type, &report->key_length);
break;
case NTP_AUTH_NTS:
NNC_GetReport(instance->nts, report);
break;
default:
assert(0);
}
}

View file

@ -29,6 +29,7 @@
#include "addressing.h"
#include "ntp.h"
#include "reports.h"
typedef struct NAU_Instance_Record *NAU_Instance;
@ -89,4 +90,7 @@ extern void NAU_ChangeAddress(NAU_Instance instance, IPAddr *address);
/* Save authentication-specific data to speed up the next start */
extern void NAU_DumpData(NAU_Instance instance);
/* Provide a report about the current authentication state */
extern void NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report);
#endif

View file

@ -2444,6 +2444,14 @@ NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *n
/* ================================================== */
void
NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report)
{
NAU_GetReport(inst->auth, report);
}
/* ================================================== */
void
NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report)
{

View file

@ -122,6 +122,7 @@ extern void NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target);
extern void NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples);
extern void NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *now);
extern void NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report);
extern void NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report);
extern int NCR_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all);

View file

@ -1249,6 +1249,24 @@ NSR_ReportSource(RPT_SourceReport *report, struct timespec *now)
}
}
/* ================================================== */
int
NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report)
{
NTP_Remote_Address rem_addr;
int slot, found;
rem_addr.ip_addr = *address;
rem_addr.port = 0;
find_slot(&rem_addr, &slot, &found);
if (!found)
return 0;
NCR_GetAuthReport(get_record(slot)->data, report);
return 1;
}
/* ================================================== */
/* The ip address is assumed to be completed on input, that is how we
identify the source record. */

View file

@ -136,6 +136,8 @@ extern int NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, IPAd
extern void NSR_ReportSource(RPT_SourceReport *report, struct timespec *now);
extern int NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report);
extern int NSR_GetNTPReport(RPT_NTPReport *report);
extern void NSR_GetActivityReport(RPT_ActivityReport *report);

View file

@ -633,3 +633,20 @@ NNC_DumpData(NNC_Instance inst)
{
save_cookies(inst);
}
/* ================================================== */
void
NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report)
{
report->key_id = inst->context_id;
report->key_type = inst->context.algorithm;
report->key_length = 8 * inst->context.s2c.length;
report->ke_attempts = inst->nke_attempts;
if (report->key_length > 0)
report->last_ke_ago = SCH_GetLastEventMonoTime() - inst->last_nke_success;
else
report->last_ke_ago = -1;
report->cookies = inst->num_cookies;
report->nak = inst->nak_response;
}

View file

@ -29,6 +29,7 @@
#include "addressing.h"
#include "ntp.h"
#include "reports.h"
typedef struct NNC_Instance_Record *NNC_Instance;
@ -45,4 +46,6 @@ extern void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address);
extern void NNC_DumpData(NNC_Instance inst);
extern void NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report);
#endif

View file

@ -124,6 +124,7 @@ static const struct request_length request_lengths[] = {
REQ_LENGTH_ENTRY(ntp_source_name,
ntp_source_name), /* NTP_SOURCE_NAME */
REQ_LENGTH_ENTRY(null, null), /* RESET_SOURCES */
REQ_LENGTH_ENTRY(auth_data, auth_data), /* AUTH_DATA */
};
static const uint16_t reply_lengths[] = {
@ -147,6 +148,7 @@ static const uint16_t reply_lengths[] = {
RPY_LENGTH_ENTRY(manual_timestamp), /* MANUAL_TIMESTAMP2 */
RPY_LENGTH_ENTRY(manual_list), /* MANUAL_LIST2 */
RPY_LENGTH_ENTRY(ntp_source_name), /* NTP_SOURCE_NAME */
RPY_LENGTH_ENTRY(auth_data), /* AUTH_DATA */
};
/* ================================================== */

View file

@ -160,4 +160,15 @@ typedef struct {
uint32_t total_valid_count;
} RPT_NTPReport;
typedef struct {
NTP_AuthMode mode;
uint32_t key_id;
int key_type;
int key_length;
int ke_attempts;
uint32_t last_ke_ago;
int cookies;
int nak;
} RPT_AuthReport;
#endif /* GOT_REPORTS_H */

View file

@ -525,6 +525,11 @@ NNC_DumpData(NNC_Instance inst)
{
}
void
NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report)
{
}
void
NKC_Initialise(void)
{

View file

@ -76,6 +76,7 @@ check_chronyc_output "^Reference ID : C0A87B01 \(node1\.net1\.clk\)" \
server_strata=0
chronyc_start=0
client_conf=""
server_conf="server 192.168.123.1"
limit=1
for chronyc_conf in \
@ -93,6 +94,7 @@ for chronyc_conf in \
"allow ::/0" \
"allow" \
"allow all 10/24" \
"authdata" \
"burst 5/10" \
"burst 3/5 255.255.255.0/1.2.3.0" \
"burst 1/2 1.2.3.0/24" \