heartbeat

Simple server monitor system using encrypted messages over udp
git clone https://noulin.net/git/heartbeat.git
Log | Files | Refs | README

commit be690bc5eb12fd7280b1ebf73d7b82113db7f94c
parent cf5d2406695bca115148d801a5dac4cc9a3d14f0
Author: Remy Noulin <loader2x@gmail.com>
Date:   Sat, 15 Jul 2023 09:38:20 +0200

stop using clock to avoid replay attacks, use a packet counter instead

this is because the clock jumps on some virtual machines in the cloud

heartbeat.c | 67 +++++++++++++++++++++++++++++++++++--------------------------
1 file changed, 39 insertions(+), 28 deletions(-)

Diffstat:
Mheartbeat.c | 67+++++++++++++++++++++++++++++++++++++++----------------------------
1 file changed, 39 insertions(+), 28 deletions(-)

diff --git a/heartbeat.c b/heartbeat.c @@ -52,6 +52,9 @@ change (event). #define PUBLIC_KEY "public.bin" #define LOGGER_PUBLIC_KEY "loggerPublic.bin" +// counter filename for encryption to avoid replay attacks +// using the clock fails (as in previous commit) because on some VMs, the clock jumps +#define counterFilename "counter.bin" #define defaultPeriod 1 // TODO #define defaultPeriod 120 @@ -594,6 +597,13 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic // send mail // when agent is not logger, forward message + u64 counter = 0; + cleanCharP(counterfn) = expandHome(uHome home counterFilename); + if (isPath(counterfn)) { + // load counter value from disk + pError(bLReadFile(counterfn, &counter, sizeof(counter))); + } + keyst keys = init0Var; // load keys @@ -714,7 +724,6 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic // open log files and load agent public keys int agentf[lenG(agents)]; u8 agentPublicKeys[lenG(agents)][sizeof(keys.remotePublicKey)]; - i64 drift[lenG(agents)]; // drift time, initialize after first packet from an agent if (logger) { arange(i, agentf) { agentf[i] = open(agentdbs[i], O_APPEND|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); @@ -737,6 +746,7 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic setG(d, "rebooted", FALSE); setG(d, "lastBoot", startTime); setG(d, "time", 0); // last incoming message time + setG(d, "count", 0); // packet counter from agent to avoid replay attacks setG(d, "c", 0); // packet counter under a period setG(d, "mono", 0); // current period start time setG(d, "net", FALSE); // there is a network issue, when agent goes from down to alive and next mId is higher than last mId @@ -814,7 +824,7 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic u32 info; } msgt; typ struct PACKED { - u64 time; + u64 count; msgt m; } payloadt; typ struct PACKED { @@ -880,8 +890,9 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic randombytes_buf(keys.nonce, sizeof(keys.nonce)); memcpy(packet.nonce, keys.nonce, sizeof(keys.nonce)); payloadt payload; - // set timestamp in encrypted message to avoid replay attacks - payload.time = getCurrentUnixTime() /*- 3 */; // uncomment to introduce an error + // set counter in encrypted message to avoid replay attacks + payload.count = counter++; + pError0(writeFile(counterfn, &counter, sizeof(counter))); payload.m = msg; packet.len = selPublicEncrypt(packet.buf, sizeof(packet.buf), (u8*)&payload, sizeof(payload), &keys); // send message @@ -1025,7 +1036,8 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic int len = selPublicDecrypt((u8*)&payload, sizeof(payload), (u8*)&data.buf, data.len, &keys); if (!len) { - logE("failed to decrypt"); + char *ip = inet_ntoa((client).sin_addr); + logE("failed to decrypt, ip %s", ip); // drop packet goto handleEventLoopSleep; } @@ -1033,17 +1045,14 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic // update state for this agent cleanFinishSmallDictP(agent) = getG(completeCfg, rtSmallDictt, agents[data.id]); - // packet is valid for 2 seconds - u64 now = getCurrentUnixTime(); - if (u$(agent, "time") == 0) { - // initialize drift time - drift[data.id] = (i64)now - (i64)payload.time; - } - - if (payload.time + drift[data.id] < now - 2 or payload.time + drift[data.id] > now + 2) { - logW("Dropping packet. Wrong timestamp %"PRIu64" now %"PRIu64" diff %"PRIi64, payload.time, now, (i64)now - (i64)payload.time + drift[data.id]); + // packet is valid only if the counter is higher than the previous one + if (u$(agent, "count") and payload.count <= u$(agent, "count")) { + // packet counter is already initialized + char *ip = inet_ntoa((client).sin_addr); + logW("Dropping packet. Wrong remote packet count %"PRIu64" local %"PRIu64" diff %"PRIi64", ip %s", payload.count, u$(agent, "count"), (i64)u$(agent, "count") - (i64)payload.count, ip); goto handleEventLoopSleep; } + setG(agent, "count", payload.count); // message is decrypted msgt *m = &payload.m; @@ -1053,7 +1062,7 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic bool saveEvent = no; record.m = *m; - record.time = now; + record.time = getCurrentUnixTime(); cleanAllocateSmallArray(mailMsg); @@ -1081,12 +1090,12 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic } } bool loggerRunning = u$(agent, "time"); - setG(agent, "time", now); + setG(agent, "time", record.time); char *newstate = m->messageId ? "alive" : "init"; if (m->messageId < u$(agent," // last message id, if next mId under last one, the agent rebootedmId")) { saveEvent = yes; setG(agent, "rebooted", TRUE); - setG(agent, "lastBoot", now); + setG(agent, "lastBoot", record.time); // send mail when agent rebooted cleanCharP(s) = formatS("%s rebooted", agents[data.id]); pushG(mailMsg, s); @@ -1103,7 +1112,7 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic // in that case mails are not sent because loggerRunning is false saveEvent = yes; setG(agent, "net", TRUE); - setG(agent, "lastNet", now); + setG(agent, "lastNet", record.time); // send mail when agent has network issues cleanCharP(s) = formatS("%s has network issues", agents[data.id]); pushG(mailMsg, s); @@ -1114,7 +1123,7 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic if (!eqG($(agent, "state"), newstate)) { saveEvent = yes; setG(agent, "state", newstate); - setG(agent, "last", now); + setG(agent, "last", record.time); } cleanFinishSmallArrayP(probes) = getG(agent, rtSmallArrayt, "probes"); if (probes) { @@ -1124,7 +1133,7 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic if (getG(p, rtBool, "state") != newstate) { saveEvent = yes; setG(p, "state", newstate); - setG(p, "last", now); + setG(p, "last", record.time); // send mail when service is down char *state = newstate ? "down" : "up"; char *result = newstate ? "failed" : "ok"; @@ -1160,14 +1169,16 @@ void probe(char *cfgfile, char *secretFile, char *publicFile, char *loggerPublic sendMail(mutt); } - // send to monitor - //lv(completeCfg); - cleanCharP(monitorData) = toStringG(completeCfg); - if (sendto(sock, monitorData, lenG(monitorData)+1, 0, (const struct sockaddr *)&server, sizeof(server)) < 0) { - logE("monitor send failed: %s", strerror(errno)); - close(sock); - XFailure; - } + // disable this, send to monitor only once for every period + // here it updates monitor for every packet received + /* // send to monitor */ + /* //lv(completeCfg); */ + /* cleanCharP(monitorData) = toStringG(completeCfg); */ + /* if (sendto(sock, monitorData, lenG(monitorData)+1, 0, (const struct sockaddr *)&server, sizeof(server)) < 0) { */ + /* logE("monitor send failed: %s", strerror(errno)); */ + /* close(sock); */ + /* XFailure; */ + /* } */ } } else {