diff --git a/daemon/bindings.c b/daemon/bindings.c index bde8d7a319afeb6035a26644f277fb28c92add3e..742510b16c75ad2ecbfe6e33c9af5b9c2aa4aecd 100644 --- a/daemon/bindings.c +++ b/daemon/bindings.c @@ -1226,6 +1226,10 @@ static int wrk_resolve(lua_State *L) if (options->DNSSEC_WANT) { knot_edns_set_do(pkt->opt_rr); } + if (options->DNSSEC_CD) { + knot_wire_set_cd(pkt->wire); + } + if (lua_isfunction(L, 5)) { /* Store callback in registry */ lua_pushvalue(L, 5); diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua index 8410f21bff306f12efd6133f4a08fff7413621a4..4dc309d5b436f6656a2e052a7bf9250f8751825e 100644 --- a/daemon/lua/kres-gen.lua +++ b/daemon/lua/kres-gen.lua @@ -77,6 +77,7 @@ struct kr_qflags { _Bool DNSSEC_WANT : 1; _Bool DNSSEC_BOGUS : 1; _Bool DNSSEC_INSECURE : 1; + _Bool DNSSEC_CD : 1; _Bool STUB : 1; _Bool ALWAYS_CUT : 1; _Bool DNSSEC_WEXPAND : 1; @@ -217,6 +218,8 @@ void knot_rrset_init_empty(knot_rrset_t *); uint32_t knot_rrset_ttl(const knot_rrset_t *); int knot_rrset_txt_dump(const knot_rrset_t *, char **, size_t *, const knot_dump_style_t *); int knot_rrset_txt_dump_data(const knot_rrset_t *, const size_t, char *, const size_t, const knot_dump_style_t *); +uint32_t knot_rrsig_sig_expiration(const knot_rdataset_t *, size_t); +uint32_t knot_rrsig_sig_inception(const knot_rdataset_t *, size_t); const knot_dname_t *knot_pkt_qname(const knot_pkt_t *); uint16_t knot_pkt_qtype(const knot_pkt_t *); uint16_t knot_pkt_qclass(const knot_pkt_t *); diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh index 865cd1ce6c706c6804db2b9e570659387e07f2d9..f6625c52b9a9a1114b9b25bc28428c58a640b04c 100755 --- a/daemon/lua/kres-gen.sh +++ b/daemon/lua/kres-gen.sh @@ -96,6 +96,8 @@ printf "\tchar _stub[];\n};\n" knot_rrset_ttl knot_rrset_txt_dump knot_rrset_txt_dump_data + knot_rrsig_sig_expiration + knot_rrsig_sig_inception # Packet knot_pkt_qname knot_pkt_qtype diff --git a/daemon/lua/sandbox.lua b/daemon/lua/sandbox.lua index 08be5101750cd66cac66ceb67e61e42e97446035..f90473ffb1c2d6ac6602929aeb9831eefb30348b 100644 --- a/daemon/lua/sandbox.lua +++ b/daemon/lua/sandbox.lua @@ -205,6 +205,7 @@ end -- Load embedded modules modules.load('ta_signal_query') modules.load('priming') +modules.load('detect_time_skew') -- Interactive command evaluation function eval_cmd(line, raw) diff --git a/doc/modules.rst b/doc/modules.rst index 709fee8ed0b3edec4d3999c46c7953e95ca0cb93..f8e69edc1963c9bc0c0dc7043f3a78deb0551832 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -27,3 +27,4 @@ Knot DNS Resolver modules .. include:: ../modules/dnstap/README.rst .. include:: ../modules/ta_signal_query/README.rst .. include:: ../modules/priming/README.rst +.. include:: ../modules/detect_time_skew/README.rst diff --git a/lib/rplan.h b/lib/rplan.h index 9a0998474aa6b55e5207cbcf9bc7a147fc91adbf..f87d7f2535368e25595c7302f45f3ed074d80870 100644 --- a/lib/rplan.h +++ b/lib/rplan.h @@ -44,6 +44,7 @@ struct kr_qflags { * i.e. knot_wire_set_cd(request->answer->wire). */ bool DNSSEC_BOGUS : 1; /**< Query response is DNSSEC bogus. */ bool DNSSEC_INSECURE : 1;/**< Query response is DNSSEC insecure. */ + bool DNSSEC_CD : 1; /**< CD bit in query */ bool STUB : 1; /**< Stub resolution, accept received answer as solved. */ bool ALWAYS_CUT : 1; /**< Always recover zone cut (even if cached). */ bool DNSSEC_WEXPAND : 1; /**< Query response has wildcard expansion. */ diff --git a/modules/detect_time_skew/README.rst b/modules/detect_time_skew/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..87e45fe6fc04fc817fc756eb269c15ddd34d0cc5 --- /dev/null +++ b/modules/detect_time_skew/README.rst @@ -0,0 +1,21 @@ +.. _mod-detect_time_skew: + +System time skew detector +------------------------- + +This module compares local system time with inception and expiration time +bounds in DNSSEC signatures for `. NS` records. If the local system time is +outside of these bounds, it is likely a misconfiguration which will cause +all DNSSEC validation (and resolution) to fail. + +In case of mismatch, a warning message will be logged to help with +further diagnostics. + +.. warning:: Information printed by this module can be forged by a network attacker! + System administrator MUST verify values printed by this module and + fix local system time using a trusted source. + +This module is useful for debugging purposes. It runs only once during resolver +start does not anything after that. It is enabled by default. +You may disable the module by appending +`modules.unload('detect_time_skew')` to your configuration. diff --git a/modules/detect_time_skew/detect_time_skew.lua b/modules/detect_time_skew/detect_time_skew.lua new file mode 100644 index 0000000000000000000000000000000000000000..ec84e595baa212da675a9ab112430cf833215843 --- /dev/null +++ b/modules/detect_time_skew/detect_time_skew.lua @@ -0,0 +1,77 @@ +-- Module interface +local ffi = require('ffi') +local knot = ffi.load(libknot_SONAME) + +local mod = {} +local event_id = nil + +-- Resolve callback +-- Check time validity of RRSIGs in priming query +-- luacheck: no unused args +local function check_time_callback(pkt, req) + pkt = kres.pkt_t(pkt) + if pkt:rcode() ~= kres.rcode.NOERROR then + warn("[detect_time_skew] cannot resolve '.' NS") + return nil + end + local valid_rrsigs = 0 + local section = pkt:rrsets(kres.section.ANSWER) + local now = os.time() + local time_diff = 0 + local inception = 0 + local expiration = 0 + for i = 1, #section do + local rr = section[i] + if rr.type == kres.type.RRSIG then + for k = 0, rr.rrs.rr_count - 1 do + inception = knot.knot_rrsig_sig_inception(rr.rrs, k) + expiration = knot.knot_rrsig_sig_expiration(rr.rrs, k) + if now > expiration then + -- possitive value = in the future + time_diff = now - expiration + elseif now < inception then + -- negative value = in the past + time_diff = now - inception + else + valid_rrsigs = valid_rrsigs + 1 + end + end + end + end + if valid_rrsigs == 0 then + warn("[detect_time_skew] Local system time %q seems to be at ".. + "least %u seconds in the %s. DNSSEC signatures for '.' NS ".. + "are not valid %s. Please check your system clock!", + os.date("%c", now), + math.abs(time_diff), + time_diff > 0 and "future" or "past", + time_diff > 0 and "yet" or "anymore") + elseif verbose() then + log("[detect_time_skew] Local system time %q is within ".. + "RRSIG validity interval <%q,%q>.", os.date("%c", now), + os.date("%c", inception), os.date("%c", expiration)) + end +end + +-- Make priming query and check time validty of RRSIGs. +local function check_time() + resolve(".", kres.type.NS, kres.class.IN, {"DNSSEC_WANT", "DNSSEC_CD"}, + check_time_callback) +end + +function mod.init() + if event_id then + error("Module is already loaded.") + else + event_id = event.after(0 , check_time) + end +end + +function mod.deinit() + if event_id then + event.cancel(event_id) + event_id = nil + end +end + +return mod diff --git a/modules/detect_time_skew/detect_time_skew.mk b/modules/detect_time_skew/detect_time_skew.mk new file mode 100644 index 0000000000000000000000000000000000000000..bc29deb32d5a4620afafcad1cb19b741273c94ba --- /dev/null +++ b/modules/detect_time_skew/detect_time_skew.mk @@ -0,0 +1,2 @@ +detect_time_skew_SOURCES := detect_time_skew.lua +$(call make_lua_module,detect_time_skew) diff --git a/modules/modules.mk b/modules/modules.mk index 23d597d116d4cdb3757a85f330a17684116fdd93..3df953c0a623bf8eb50d9b83f05b18c677ee0325 100644 --- a/modules/modules.mk +++ b/modules/modules.mk @@ -34,7 +34,8 @@ modules_TARGETS += etcd \ workarounds \ version \ ta_signal_query \ - priming + priming \ + detect_time_skew endif # Make C module diff --git a/tests/deckard b/tests/deckard index fcfade5d1805b7c1151523991e4d5ea68db03f03..a5cd67c98836690e00ab76b7bd220023f7993ee9 160000 --- a/tests/deckard +++ b/tests/deckard @@ -1 +1 @@ -Subproject commit fcfade5d1805b7c1151523991e4d5ea68db03f03 +Subproject commit a5cd67c98836690e00ab76b7bd220023f7993ee9