]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
modules/ta_update: move RFC5011 to a separate module
authorTomas Krizek <tomas.krizek@nic.cz>
Tue, 12 Mar 2019 17:47:35 +0000 (18:47 +0100)
committerPetr Špaček <petr.spacek@nic.cz>
Thu, 4 Apr 2019 12:18:56 +0000 (14:18 +0200)
.luacheckrc
daemon/lua/meson.build
daemon/lua/sandbox.lua
daemon/lua/trust_anchors.lua.in
modules/meson.build
modules/ta_update/ta_update.lua [new file with mode: 0644]
modules/ta_update/ta_update.test.integr/deckard.yaml [moved from daemon/lua/trust_anchors.test.integr/deckard.yaml with 72% similarity]
modules/ta_update/ta_update.test.integr/kresd_config.j2 [moved from daemon/lua/trust_anchors.test.integr/kresd_config.j2 with 100% similarity]
modules/ta_update/ta_update.test.integr/rfc5011_unsupported_key_rollover.rpl [moved from daemon/lua/trust_anchors.test.integr/rfc5011_unsupported_key_rollover.rpl with 100% similarity]

index 1729bd4b5ca18851f0d4c7d208438be09072cee7..47608f4783fdd3319cea1aff569f4d0ea54c7ab4 100644 (file)
@@ -14,6 +14,7 @@ new_read_globals = {
        'cache',
        'modules',
        'trust_anchors',
+       'ta_update',
        'worker',
        'event',
        '_hint_root_file',
index e6e4eb96334b3699098cdd602ea1ee0ad3c7c453..d11bf7731a4f94d210f87faa7d0306ce3fc08bc5 100644 (file)
@@ -5,12 +5,6 @@ config_tests += [
   ['ta_bootstrap', files('trust_anchors.test/bootstrap.test.lua')],
 ]
 
-integr_tests += [
-  # NOTE: ta_rfc5011 may fail due to race condition; to ensure it passes, deckard should
-  # introduce a timeout
-  ['ta_rfc5011', join_paths(meson.current_source_dir(), 'trust_anchors.test.integr')],
-]
-
 ta_config = configuration_data()
 ta_config.set('keyfile_default', keyfile_default)
 ta_config.set('etc_dir', etc_dir)
index 017e3a39936ce61df81ec218c6f80b77ca869e25..79ebc4a61bef80d804188ab6e3819220947c0048 100644 (file)
@@ -316,6 +316,7 @@ end
 
 -- Load embedded modules
 trust_anchors = require('trust_anchors')
+modules.load('ta_update')
 modules.load('ta_signal_query')
 modules.load('policy')
 modules.load('priming')
index 472241015a22c9777e2f255857e042e1ceaa9470..a9eb1b3affcaf7a3eae7f52e49a0ca4197a852ff 100644 (file)
@@ -5,6 +5,12 @@ local C = ffi.C
 
 local trust_anchors -- the public pseudo-module, exported as global variable
 
+-- RFC5011 state table
+local key_state = {
+       Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
+       Missing = 'Missing', Revoked = 'Revoked', Removed = 'Removed'
+}
+
 -- Fetch over HTTPS with peert cert checked
 local function https_fetch(url, ca)
        local ssl_ok, https = pcall(require, 'ssl.https')
@@ -152,179 +158,6 @@ local function bootstrap(url, ca)
        return rrset, msg
 end
 
--- RFC5011 state table
-local key_state = {
-       Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
-       Missing = 'Missing', Revoked = 'Revoked', Removed = 'Removed'
-}
-
--- Find key in current keyset
-local function ta_find(keyset, rr)
-       local rr_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
-       assert(rr_tag >= 0 and rr_tag <= 65535, string.format('invalid RR: %s: %s',
-              kres.rr2str(rr), ffi.string(C.knot_strerror(rr_tag))))
-       for i, ta in ipairs(keyset) do
-               -- Match key owner and content
-               local ta_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
-               assert(ta_tag >= 0 and ta_tag <= 65535, string.format('invalid RR: %s: %s',
-                      kres.rr2str(ta), ffi.string(C.knot_strerror(ta_tag))))
-               if ta.owner == rr.owner then
-                       if ta.type == rr.type then
-                               if rr.type == kres.type.DNSKEY then
-                                       if C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
-                                               return ta
-                                       end
-                               elseif rr.type == kres.type.DS and ta.rdata == rr.rdata then
-                                       return ta
-                               end
-                       -- DNSKEY superseding DS, inexact match
-                       elseif rr.type == kres.type.DNSKEY and ta.type == kres.type.DS then
-                               if ta.key_tag == rr_tag then
-                                       keyset[i] = rr -- Replace current DS
-                                       rr.state = ta.state
-                                       rr.key_tag = ta.key_tag
-                                       return rr
-                               end
-                       -- DS key matching DNSKEY, inexact match
-                       elseif rr.type == kres.type.DS and ta.type == kres.type.DNSKEY then
-                               if rr_tag == ta_tag then
-                                       return ta
-                               end
-                       end
-               end
-       end
-       return nil
-end
-
--- Evaluate TA status of a RR according to RFC5011.  The time is in seconds.
-local function ta_present(keyset, rr, hold_down_time, force_valid)
-       if rr.type == kres.type.DNSKEY and not C.kr_dnssec_key_ksk(rr.rdata) then
-               return false -- Ignore
-       end
-    -- Attempt to extract key_tag
-       local key_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
-    if key_tag < 0 or key_tag > 65535 then
-        warn(string.format('[ ta ] ignoring invalid or unsupported RR: %s: %s',
-            kres.rr2str(rr), ffi.string(C.knot_strerror(key_tag))))
-        return false
-    end
-       -- Find the key in current key set and check its status
-       local now = os.time()
-       local key_revoked = (rr.type == kres.type.DNSKEY) and C.kr_dnssec_key_revoked(rr.rdata)
-       local ta = ta_find(keyset, rr)
-       if ta then
-               -- Key reappears (KeyPres)
-               if ta.state == key_state.Missing then
-                       ta.state = key_state.Valid
-                       ta.timer = nil
-               end
-               -- Key is revoked (RevBit)
-               if ta.state == key_state.Valid or ta.state == key_state.Missing then
-                       if key_revoked then
-                               ta.state = key_state.Revoked
-                               ta.timer = now + hold_down_time
-                       end
-               end
-               -- Remove hold-down timer expires (RemTime)
-               if ta.state == key_state.Revoked and os.difftime(ta.timer, now) <= 0 then
-                       ta.state = key_state.Removed
-                       ta.timer = nil
-               end
-               -- Add hold-down timer expires (AddTime)
-               if ta.state == key_state.AddPend and os.difftime(ta.timer, now) <= 0 then
-                       ta.state = key_state.Valid
-                       ta.timer = nil
-               end
-               if rr.state ~= key_state.Valid or verbose() then
-                       log('[ ta ] key: ' .. key_tag .. ' state: '..ta.state)
-               end
-               return true
-       elseif not key_revoked then -- First time seen (NewKey)
-               rr.key_tag = key_tag
-               if force_valid then
-                       rr.state = key_state.Valid
-               else
-                       rr.state = key_state.AddPend
-                       rr.timer = now + hold_down_time
-               end
-               if rr.state ~= key_state.Valid or verbose() then
-                       log('[ ta ] key: ' .. key_tag .. ' state: '..rr.state)
-               end
-               table.insert(keyset, rr)
-               return true
-       end
-       return false
-end
-
--- TA is missing in the new key set.  The time is in seconds.
-local function ta_missing(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)
-       assert(key_tag >= 0 and key_tag <= 65535, string.format('invalid RR: %s: %s',
-              kres.rr2str(ta), ffi.string(C.knot_strerror(key_tag))))
-       if ta.state == key_state.Valid then
-               ta.state = key_state.Missing
-               ta.timer = os.time() + hold_down_time
-
-       -- Remove key that is missing for too long
-       elseif ta.state == key_state.Missing and os.difftime(ta.timer, os.time()) <= 0 then
-               ta.state = key_state.Removed
-               log('[ ta ] key: '..key_tag..' removed because missing for too long')
-               keep_ta = false
-
-       -- Purge pending key
-       elseif ta.state == key_state.AddPend then
-               log('[ ta ] key: '..key_tag..' purging')
-               keep_ta = false
-       end
-       log('[ ta ] key: '..key_tag..' state: '..ta.state)
-       return keep_ta
-end
-
-local active_refresh, update -- forwards
-
--- Plan an event for refreshing the root DNSKEYs and re-scheduling itself
-local function refresh_plan(keyset, delay, is_initial)
-       local owner_str = kres.dname2str(keyset.owner) -- maybe fix converting back and forth?
-       keyset.refresh_ev = event.after(delay, function ()
-               resolve(owner_str, kres.type.DNSKEY, kres.class.IN, 'NO_CACHE',
-               function (pkt)
-                       -- Schedule itself with updated timeout
-                       local delay_new = active_refresh(keyset, kres.pkt_t(pkt), is_initial)
-                       delay_new = keyset.refresh_time or trust_anchors.refresh_time or delay_new
-                       log('[ ta ] next refresh for ' .. owner_str .. ' in '
-                                       .. delay_new/hour .. ' hours')
-                       refresh_plan(keyset, delay_new)
-               end)
-       end)
-end
-
--- Refresh the DNSKEYs from the packet, and return time to the next check.
-active_refresh = function (keyset, pkt, is_initial)
-       local retry = true
-       if pkt:rcode() == kres.rcode.NOERROR then
-               local records = pkt:section(kres.section.ANSWER)
-               local new_keys = {}
-               for _, rr in ipairs(records) do
-                       if rr.type == kres.type.DNSKEY then
-                               table.insert(new_keys, rr)
-                       end
-               end
-               update(keyset, new_keys, is_initial)
-               retry = false
-       else
-               warn('[ ta ] active refresh failed for ' .. kres.dname2str(keyset.owner)
-                               .. ' with rcode: ' .. pkt:rcode())
-       end
-       -- Calculate refresh/retry timer (RFC 5011, 2.3)
-       local min_ttl = retry and day or 15 * day
-       for _, rr in ipairs(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
-
 -- Update ta.comment and return decorated line representing the RR
 -- This is meant to be in zone-file format.
 local function ta_rr_str(ta)
@@ -465,68 +298,15 @@ local function keyset_publish(keyset)
        return count > 0 and not has_error
 end
 
-
--- Update existing keyset; return true if successful.
--- Param `is_initial` (bool): force .NewKey states to .Valid, i.e. init empty keyset.
-update = function (keyset, new_keys, is_initial)
-       if not new_keys then return false end
-
-       -- Filter TAs to be purged from the keyset (KeyRem), in three steps
-       -- 1: copy TAs to be kept to `keepset`
-       local hold_down = (keyset.hold_down_time or trust_anchors.hold_down_time) / 1000
-       local keepset = {}
-       local keep_removed = keyset.keep_removed or trust_anchors.keep_removed
-       for _, ta in ipairs(keyset) do
-               local keep = true
-               if not ta_find(new_keys, ta) then
-                       -- Ad-hoc: RFC 5011 doesn't mention removing a Missing key.
-                       -- Let's do it after a very long period has elapsed.
-                       keep = ta_missing(ta, hold_down * 4)
-               end
-               -- Purge removed keys
-               if ta.state == key_state.Removed then
-                       if keep_removed > 0 then
-                               keep_removed = keep_removed - 1
-                       else
-                               keep = false
-                       end
-               end
-               if keep then
-                       table.insert(keepset, ta)
-               end
-       end
-       -- 2: remove all TAs - other settings etc. will remain in the keyset
-       for i, _ in ipairs(keyset) do
-               keyset[i] = nil
-       end
-       -- 3: move TAs to be kept into the keyset (same indices)
-       for k, ta in pairs(keepset) do
-               keyset[k] = ta
-       end
-
-       -- Evaluate new TAs
-       for _, rr in ipairs(new_keys) do
-               if (rr.type == kres.type.DNSKEY or rr.type == kres.type.DS) and rr.rdata ~= nil then
-                       ta_present(keyset, rr, hold_down, is_initial)
-               end
-       end
-
-       -- Store the keyset
-       keyset_write(keyset)
-
-       -- Start using the new TAs.
-       if not keyset_publish(keyset) then
-               -- TODO: try to rebootstrap if for root?
-               return false
-       elseif verbose() then
-               log('[ ta ] refreshed trust anchors for domain ' .. kres.dname2str(keyset.owner) .. ' are:\n'
-                   .. trust_anchors.summary(keyset.owner))
-       end
-
-       return true
+local refresh_plan = function(keyset, delay, is_initial)
+    event.after(0, function()
+        if ta_update ~= nil then
+            ta_update.refresh_plan(keyset, delay, is_initial)
+        end
+    end)
 end
 
-local add_file = function (path, unmanaged)
+local function add_file(path, unmanaged)
        if not unmanaged then
                if not io.open(path .. '.lock', 'w') then
                        error("[ ta ] ERROR: write access needed to keyfile dir '"..path.."'")
@@ -613,16 +393,11 @@ trust_anchors = {
        --   - owner - that dname (for simplicity)
        --   - [optional] filename in which to persist the state,
        --     implying unmanaged TA if nil
-       --   - [optional] overrides for global defaults of
-       --     hold_down_time, refresh_time, keep_removed
        -- The RR tables also contain some additional TA-specific fields.
        keysets = {},
 
        -- Documented properties:
        insecure = {},
-       hold_down_time = 30 * day,
-       refresh_time = nil,
-       keep_removed = 0,
 
        bootstrap_url = 'https://data.iana.org/root-anchors/root-anchors.xml',
        bootstrap_ca = '@etc_dir@/icann-ca.pem',
@@ -633,6 +408,9 @@ trust_anchors = {
        add_file = add_file,
        config = add_file,
 
+    keyset_write = keyset_write,
+    keyset_publish = keyset_publish,
+
        -- Add DS/DNSKEY record(s) (unmanaged)
        add = function (keystr)
                local keyset, err = keyset_read(nil, keystr)
index 0c3d7fb0dfad059c7653b097dd30cec593bbf16b..dcadcbd82a377f7afe641794ed13a0eff199bdc5 100644 (file)
@@ -14,6 +14,7 @@ lua_mod_src = [  # add lua modules without separate meson.build
   files('serve_stale/serve_stale.lua'),
   files('ta_sentinel/ta_sentinel.lua'),
   files('ta_signal_query/ta_signal_query.lua'),
+  files('ta_update/ta_update.lua'),
   files('workarounds/workarounds.lua'),
 ]
 
@@ -28,6 +29,9 @@ integr_tests += [
   ['bogus_log', join_paths(meson.current_source_dir(), 'bogus_log', 'test.integr')],
   ['rebinding', join_paths(meson.current_source_dir(), 'rebinding', 'test.integr')],
   ['serve_stale', join_paths(meson.current_source_dir(), 'serve_stale', 'test.integr')],
+  # NOTE: ta_update may pass in cases when it should fail due to race conditions
+  # To ensure reliability, deckard should introduce a time wait
+  ['ta_update', join_paths(meson.current_source_dir(), 'ta_update', 'ta_update.test.integr')],
 ]
 
 
diff --git a/modules/ta_update/ta_update.lua b/modules/ta_update/ta_update.lua
new file mode 100644 (file)
index 0000000..c683bbf
--- /dev/null
@@ -0,0 +1,248 @@
+-- Module interface
+local ffi = require('ffi')
+local kres = require('kres')
+local C = ffi.C
+
+local ta_update = {}
+
+-- RFC5011 state table
+local key_state = {
+       Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
+       Missing = 'Missing', Revoked = 'Revoked', Removed = 'Removed'
+}
+
+-- Find key in current keyset
+local function ta_find(keyset, rr)
+       local rr_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
+       assert(rr_tag >= 0 and rr_tag <= 65535, string.format('invalid RR: %s: %s',
+              kres.rr2str(rr), ffi.string(C.knot_strerror(rr_tag))))
+       for i, ta in ipairs(keyset) do
+               -- Match key owner and content
+               local ta_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
+               assert(ta_tag >= 0 and ta_tag <= 65535, string.format('invalid RR: %s: %s',
+                      kres.rr2str(ta), ffi.string(C.knot_strerror(ta_tag))))
+               if ta.owner == rr.owner then
+                       if ta.type == rr.type then
+                               if rr.type == kres.type.DNSKEY then
+                                       if C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
+                                               return ta
+                                       end
+                               elseif rr.type == kres.type.DS and ta.rdata == rr.rdata then
+                                       return ta
+                               end
+                       -- DNSKEY superseding DS, inexact match
+                       elseif rr.type == kres.type.DNSKEY and ta.type == kres.type.DS then
+                               if ta.key_tag == rr_tag then
+                                       keyset[i] = rr -- Replace current DS
+                                       rr.state = ta.state
+                                       rr.key_tag = ta.key_tag
+                                       return rr
+                               end
+                       -- DS key matching DNSKEY, inexact match
+                       elseif rr.type == kres.type.DS and ta.type == kres.type.DNSKEY then
+                               if rr_tag == ta_tag then
+                                       return ta
+                               end
+                       end
+               end
+       end
+       return nil
+end
+
+-- Evaluate TA status of a RR according to RFC5011.  The time is in seconds.
+local function ta_present(keyset, rr, hold_down_time, force_valid)
+if rr.type == kres.type.DNSKEY and not C.kr_dnssec_key_ksk(rr.rdata) then
+    return false -- Ignore
+end
+-- Attempt to extract key_tag
+local key_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
+if key_tag < 0 or key_tag > 65535 then
+    warn(string.format('[ ta_update ] ignoring invalid or unsupported RR: %s: %s',
+        kres.rr2str(rr), ffi.string(C.knot_strerror(key_tag))))
+    return false
+end
+-- Find the key in current key set and check its status
+local now = os.time()
+local key_revoked = (rr.type == kres.type.DNSKEY) and C.kr_dnssec_key_revoked(rr.rdata)
+local ta = ta_find(keyset, rr)
+if ta then
+    -- Key reappears (KeyPres)
+    if ta.state == key_state.Missing then
+        ta.state = key_state.Valid
+        ta.timer = nil
+    end
+    -- Key is revoked (RevBit)
+    if ta.state == key_state.Valid or ta.state == key_state.Missing then
+        if key_revoked then
+            ta.state = key_state.Revoked
+            ta.timer = now + hold_down_time
+        end
+    end
+    -- Remove hold-down timer expires (RemTime)
+    if ta.state == key_state.Revoked and os.difftime(ta.timer, now) <= 0 then
+        ta.state = key_state.Removed
+        ta.timer = nil
+    end
+    -- Add hold-down timer expires (AddTime)
+    if ta.state == key_state.AddPend and os.difftime(ta.timer, now) <= 0 then
+        ta.state = key_state.Valid
+        ta.timer = nil
+    end
+    if rr.state ~= key_state.Valid or verbose() then
+        log('[ ta_update ] key: ' .. key_tag .. ' state: '..ta.state)
+    end
+    return true
+elseif not key_revoked then -- First time seen (NewKey)
+    rr.key_tag = key_tag
+    if force_valid then
+                       rr.state = key_state.Valid
+               else
+                       rr.state = key_state.AddPend
+                       rr.timer = now + hold_down_time
+               end
+               if rr.state ~= key_state.Valid or verbose() then
+                       log('[ ta_update ] key: ' .. key_tag .. ' state: '..rr.state)
+               end
+               table.insert(keyset, rr)
+               return true
+       end
+       return false
+end
+
+-- TA is missing in the new key set.  The time is in seconds.
+local function ta_missing(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)
+       assert(key_tag >= 0 and key_tag <= 65535, string.format('invalid RR: %s: %s',
+              kres.rr2str(ta), ffi.string(C.knot_strerror(key_tag))))
+       if ta.state == key_state.Valid then
+               ta.state = key_state.Missing
+               ta.timer = os.time() + hold_down_time
+
+       -- Remove key that is missing for too long
+       elseif ta.state == key_state.Missing and os.difftime(ta.timer, os.time()) <= 0 then
+               ta.state = key_state.Removed
+               log('[ ta_update ] key: '..key_tag..' removed because missing for too long')
+               keep_ta = false
+
+       -- Purge pending key
+       elseif ta.state == key_state.AddPend then
+               log('[ ta_update ] key: '..key_tag..' purging')
+               keep_ta = false
+       end
+       log('[ ta_update ] key: '..key_tag..' state: '..ta.state)
+       return keep_ta
+end
+
+-- Update existing keyset; return true if successful.
+-- Param `is_initial` (bool): force .NewKey states to .Valid, i.e. init empty keyset.
+local function update(keyset, new_keys, is_initial)
+       if not new_keys then return false end
+
+       -- Filter TAs to be purged from the keyset (KeyRem), in three steps
+       -- 1: copy TAs to be kept to `keepset`
+       local hold_down = (keyset.hold_down_time or ta_update.hold_down_time) / 1000
+       local keepset = {}
+       local keep_removed = keyset.keep_removed or ta_update.keep_removed
+       for _, ta in ipairs(keyset) do
+               local keep = true
+               if not ta_find(new_keys, ta) then
+                       -- Ad-hoc: RFC 5011 doesn't mention removing a Missing key.
+                       -- Let's do it after a very long period has elapsed.
+                       keep = ta_missing(ta, hold_down * 4)
+               end
+               -- Purge removed keys
+               if ta.state == key_state.Removed then
+                       if keep_removed > 0 then
+                               keep_removed = keep_removed - 1
+                       else
+                               keep = false
+                       end
+               end
+               if keep then
+                       table.insert(keepset, ta)
+               end
+       end
+       -- 2: remove all TAs - other settings etc. will remain in the keyset
+       for i, _ in ipairs(keyset) do
+               keyset[i] = nil
+       end
+       -- 3: move TAs to be kept into the keyset (same indices)
+       for k, ta in pairs(keepset) do
+               keyset[k] = ta
+       end
+
+       -- Evaluate new TAs
+       for _, rr in ipairs(new_keys) do
+               if (rr.type == kres.type.DNSKEY or rr.type == kres.type.DS) and rr.rdata ~= nil then
+                       ta_present(keyset, rr, hold_down, is_initial)
+               end
+       end
+
+       -- Store the keyset
+       trust_anchors.keyset_write(keyset)
+
+       -- Start using the new TAs.
+       if not trust_anchors.keyset_publish(keyset) then
+               -- TODO: try to rebootstrap if for root?
+               return false
+       elseif verbose() then
+               log('[ ta_update ] refreshed trust anchors for domain ' .. kres.dname2str(keyset.owner) .. ' are:\n'
+                   .. trust_anchors.summary(keyset.owner))
+       end
+
+       return true
+end
+
+-- Refresh the DNSKEYs from the packet, and return time to the next check.
+local function active_refresh(keyset, pkt, is_initial)
+       local retry = true
+       if pkt:rcode() == kres.rcode.NOERROR then
+               local records = pkt:section(kres.section.ANSWER)
+               local new_keys = {}
+               for _, rr in ipairs(records) do
+                       if rr.type == kres.type.DNSKEY then
+                               table.insert(new_keys, rr)
+                       end
+               end
+               update(keyset, new_keys, is_initial)
+               retry = false
+       else
+               warn('[ ta_update ] active refresh failed for ' .. kres.dname2str(keyset.owner)
+                               .. ' with rcode: ' .. pkt:rcode())
+       end
+       -- Calculate refresh/retry timer (RFC 5011, 2.3)
+       local min_ttl = retry and day or 15 * day
+       for _, rr in ipairs(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
+
+-- Plan an event for refreshing the root DNSKEYs and re-scheduling itself
+local function refresh_plan(keyset, delay, is_initial)
+       local owner_str = kres.dname2str(keyset.owner) -- maybe fix converting back and forth?
+       keyset.refresh_ev = event.after(delay, function ()
+               resolve(owner_str, kres.type.DNSKEY, kres.class.IN, 'NO_CACHE',
+               function (pkt)
+                       -- Schedule itself with updated timeout
+                       local delay_new = active_refresh(keyset, kres.pkt_t(pkt), is_initial)
+                       delay_new = keyset.refresh_time or ta_update.refresh_time or delay_new
+                       log('[ ta_update ] next refresh for ' .. owner_str .. ' in '
+                                       .. delay_new/hour .. ' hours')
+                       refresh_plan(keyset, delay_new)
+               end)
+       end)
+end
+
+ta_update = {
+    --   - [optional] overrides for global defaults of
+    --     hold_down_time, refresh_time, keep_removed
+    hold_down_time = 30 * day,
+    refresh_time = nil,
+    keep_removed = 0,
+    refresh_plan = refresh_plan,
+}
+
+return ta_update
similarity index 72%
rename from daemon/lua/trust_anchors.test.integr/deckard.yaml
rename to modules/ta_update/ta_update.test.integr/deckard.yaml
index 4b71a6ba39241e91c1ca90ee06d01fa6c2aab693..6906eeb1b977f9b50faaac68244766d135035e27 100644 (file)
@@ -5,7 +5,7 @@ programs:
     - -f
     - "1"
   templates:
-    - daemon/lua/trust_anchors.test.integr/kresd_config.j2
+    - modules/ta_update/ta_update.test.integr/kresd_config.j2
     - tests/integration/hints_zone.j2
   configs:
     - config