]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon/trust_anchors: active refresh, timers, managed file
authorMarek Vavruša <marek.vavrusa@nic.cz>
Sun, 27 Sep 2015 22:05:23 +0000 (00:05 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Sun, 27 Sep 2015 22:05:23 +0000 (00:05 +0200)
daemon/lua/trust_anchors.lua

index 7f3c287bc8887c66ff96eb1ea597e80809e24838..cfc7a9ecc55b2c89354f48a38d31862898f39159 100644 (file)
@@ -1,9 +1,6 @@
 local kres = require('kres')
 local C = require('ffi').C
 
--- Add or remove hold-down timer
-local hold_down_time = 30 * day
-
 -- RFC5011 state table
 local key_state = {
        Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
@@ -16,7 +13,7 @@ local function ta_find(keyset, rr)
                local ta = keyset[i]
                -- Match key owner and content
                if ta.owner == rr.owner and
-                  C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) then
+                  C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
                   return ta
                end
        end
@@ -24,10 +21,10 @@ local function ta_find(keyset, rr)
 end
 
 -- Evaluate TA status according to RFC5011
-local function ta_present(keyset, rr, force)
+local function ta_present(keyset, rr, hold_down_time, force)
        if not C.kr_dnssec_key_ksk(rr.rdata) then
                return false -- Ignore
-       end     
+       end
        -- Find the key in current key set and check its status
        local now = os.time()
        local key_revoked = C.kr_dnssec_key_revoked(rr.rdata)
@@ -59,6 +56,7 @@ local function ta_present(keyset, rr, force)
                print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
                return true
        elseif not key_revoked then -- First time seen (NewKey)
+               rr.key_tag = key_tag
                if force then
                        rr.state = key_state.Valid
                else
@@ -73,7 +71,7 @@ local function ta_present(keyset, rr, force)
 end
 
 -- TA is missing in the new key set
-local function ta_missing(keyset, ta)
+local function ta_missing(keyset, ta, hold_down_time)
        -- Key is removed (KeyRem)
        local keep_ta = true
        local key_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
@@ -89,24 +87,79 @@ local function ta_missing(keyset, ta)
        return keep_ta
 end
 
+-- Plan refresh event and re-schedule itself based on the result of the callback
+local function refresh_plan(trust_anchors, timeout, refresh_cb)
+       if trust_anchors.refresh_ev ~= nil then event.cancel(trust_anchors.refresh_ev) end
+       trust_anchors.refresh_ev = event.after(timeout, function (ev)
+               worker.resolve('.', kres.type.DNSKEY, kres.class.IN, kres.query.NO_CACHE,
+               function (pkt)
+                       -- Schedule itself with updated timeout
+                       local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt))
+                       next_time = math.min(next_time, trust_anchors.refresh_time)
+                       print('[trust_anchors] next refresh: '..next_time)
+                       refresh_plan(trust_anchors, next_time, refresh_cb)
+               end)
+       end)
+end
+
+-- Active refresh, return time of the next check
+local function active_refresh(trust_anchors, pkt)
+       local retry = true
+       if pkt:rcode() == kres.rcode.NOERROR then
+               local records = pkt:section(kres.section.ANSWER)
+               local keyset = {}
+               for i = 1, #records do
+                       local rr = records[i]
+                       if rr.type == kres.type.DNSKEY then
+                               table.insert(keyset, rr)
+                       end
+               end
+               trust_anchors.update(keyset, false)
+               retry = false
+       end
+       -- Calculate refresh/retry timer (RFC 5011, 2.3)
+       local min_ttl = retry and day or 15 * day
+       for i, rr in ipairs(trust_anchors.keyset) do -- 10 or 50% of the original TTL
+               min_ttl = math.min(min_ttl, (retry and 100 or 500) * rr.ttl)
+       end
+       return math.max(hour, min_ttl)
+end
+
+-- Write keyset to a file
+local function keyset_write(keyset, path)
+       local file = assert(io.open(path..'.lock', 'w'))
+       for i = 1, #keyset do
+               local ta = keyset[i]
+               local rr_str = string.format('%s ; %s\n', kres.rr2str(ta), ta.state)
+               if ta.state ~= key_state.Valid and ta.state ~= key_state.Missing then
+                       rr_str = '; '..rr_str -- Invalidate key string
+               end
+               file:write(rr_str)
+       end
+       file:close()
+       os.rename(path..'.lock', path)
+end
+
 -- TA store management
 local trust_anchors = {
        keyset = {},
        insecure = {},
+       hold_down_time = 30 * day,
        -- Update existing keyset
        update = function (new_keys, initial)
                if not new_keys then return false end
                -- Filter TAs to be purged from the keyset (KeyRem)
+               local hold_down = trust_anchors.hold_down_time / 1000
                local keyset_keep = {}
                local keyset = trust_anchors.keyset
                for i = 1, #keyset do
                        local ta = keyset[i]
                        local keep = true
                        if not ta_find(new_keys, ta) then
-                               keep = ta_missing(keyset, ta)
+                               keep = ta_missing(trust_anchors, keyset, ta, hold_down)
                        end
                        if keep then
-                               table.insert(keyset_keep, rr)
+                               table.insert(keyset_keep, ta)
                        end
                end
                keyset = keyset_keep
@@ -114,12 +167,13 @@ local trust_anchors = {
                for i = 1, #new_keys do
                        local rr = new_keys[i]
                        if rr.type == kres.type.DNSKEY then
-                               ta_present(keyset, rr, initial)
+                               ta_present(keyset, rr, hold_down, initial)
                        end
                end
                -- Publish active TAs
                local store = kres.context().trust_anchors
                C.kr_ta_clear(store)
+               if #keyset == 0 then return false end
                for i = 1, #keyset do
                        local ta = keyset[i]
                        -- Key MAY be used as a TA only in these two states (RFC5011, 4.2)
@@ -128,12 +182,21 @@ local trust_anchors = {
                        end
                end
                trust_anchors.keyset = keyset
+               -- Store keyset in the file
+               if trust_anchors.file_current ~= nil then
+                       keyset_write(keyset, trust_anchors.file_current)
+               end
                return true
        end,
        -- Load keys from a file (managed)
-       config = function (path)
+       config = function (path, is_unmanaged)
                local new_keys = require('zonefile').parse_file(path)
-               trust_anchors.update(new_keys, true)
+               trust_anchors.file_current = path
+               if is_unmanaged then trust_anchors.file_current = nil end
+               trust_anchors.keyset = {}
+               if trust_anchors.update(new_keys, true) then
+                       refresh_plan(trust_anchors, sec, active_refresh)
+               end
        end,
        -- Add DS/DNSKEY record(s) (unmanaged)
        add = function (keystr)