if the root key file doesn’t exist, it will be populated from root DNSKEY query, which will be validated against root trust anchors retrieved over HTTPS with IANA cert verification against built-in current IANA cert CA. it requires luasocket and luasec for it to work. trust anchors XML file signature is not checked, as there’s no facility for PKCS7 checking yet.
# Targets
all: info lib daemon modules
-install: lib-install daemon-install modules-install
+install: lib-install daemon-install modules-install etc-install
check: all tests
clean: lib-clean daemon-clean modules-clean tests-clean doc-clean
doc: doc-html
$(info PREFIX: $(PREFIX))
$(info BINDIR: $(BINDIR))
$(info LIBDIR: $(LIBDIR))
+ $(info ETCDIR: $(ETCDIR))
$(info INCLUDEDIR: $(INCLUDEDIR))
$(info MODULEDIR: $(MODULEDIR))
$(info )
$(info [$(HAS_socket_wrapper)] socket_wrapper (lib))
$(info )
-# Moduledir
+# Installation directories
$(PREFIX)/$(MODULEDIR):
- $(INSTALL) -d $(PREFIX)/$(MODULEDIR)
+ $(INSTALL) -d $@
moduledir: $(PREFIX)/$(MODULEDIR)
+$(PREFIX)/$(ETCDIR):
+ $(INSTALL) -m 0750 -d $@
+etcdir: $(PREFIX)/$(ETCDIR)
# Sub-targets
include lib/lib.mk
include modules/modules.mk
include tests/tests.mk
include doc/doc.mk
+include etc/etc.mk
LIBDIR := /lib
INCLUDEDIR := /include
MODULEDIR := $(LIBDIR)/kdns_modules
+ETCDIR := /etc/kresd
# Tools
CC ?= cc
BUILD_LDFLAGS += $(LDFLAGS)
BUILD_CFLAGS := $(CFLAGS) -std=c99 -D_GNU_SOURCE -fPIC -Wtype-limits -Wall -I$(abspath .) -I$(abspath lib/generic) -I$(abspath contrib)
-BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\""
+BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\"" -DETCDIR="\"$(ETCDIR)\""
RM := rm -f
LN := ln -s
XXD := ./scripts/embed.sh
===============
The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates and :rfc:`7646` negative trust anchors.
-To enable it, you need to provide at least _one_ trust anchor. This step is not automatic, as you're supposed to obtain
-the trust anchor `using a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_.
-From there, the Knot DNS Resolver can perform automatic updates for you.
+To enable it, you need to provide trusted root keys. Bootstrapping of the keys is automated, and kresd fetches root trust anchors set `over a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_ from IANA. From there, it can perform :rfc:`5011` automatic updates for you.
+
+.. note:: Automatic bootstrap requires luasocket_ and luasec_ installed.
+
+.. code-block:: bash
+
+ $ kresd -k root.keys # File for root keys
+ [ ta ] bootstrapped root anchor "19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5"
+ [ ta ] warning: you SHOULD check the key manually, see: https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs
+ [ ta ] key: 19036 state: Valid
+ [ ta ] next refresh: 86400000
+
+Alternatively, you can set it in configuration file with ``trust_anchors.file = 'root.keys'``. If the file doesn't exist, it will be automatically populated with root keys validated using root anchors retrieved over HTTPS.
+
+This is equivalent to `using unbound-anchor <https://www.unbound.net/documentation/howto_anchor.html>`_:
+
+.. code-block:: bash
+
+ $ unbound-anchor -a "root.keys" || echo "warning: check the key at this point"
+ $ echo "auto-trust-anchor-file: \"root.keys\"" >> unbound.conf
+ $ unbound -c unbound.conf
+
+.. warning:: Bootstrapping of the root trust anchors is automatic, you are however **encouraged to check** the key over **secure channel**, as specified in `DNSSEC Trust Anchor Publication for the Root Zone <https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs>`_. This is a critical step where the whole infrastructure may be compromised, you will be warned in the server log.
+
+Manually providing root anchors
+-------------------------------
+
+The root anchors bootstrap may fail for various reasons, in this case you need to provide IANA or alternative root anchors. The format of the keyfile is the same as for Unbound or BIND and contains DNSKEY records.
1. Check the current TA published on `IANA website <https://data.iana.org/root-anchors/root-anchors.xml>`_
2. Fetch current keys (DNSKEY), verify digests
.. _LuaRocks: https://rocks.moonscript.org/
.. _libuv: https://github.com/libuv/libuv
.. _Lua: http://www.lua.org/about.html
-.. _LuaJIT: http://luajit.org/luajit.html
\ No newline at end of file
+.. _LuaJIT: http://luajit.org/luajit.html
+.. _luasec: https://luarocks.org/modules/luarocks/luasec
+.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
\ No newline at end of file
lua_setglobal(engine->L, "user");
lua_pushcfunction(engine->L, l_libpath);
lua_setglobal(engine->L, "libpath");
+ lua_pushliteral(engine->L, PREFIX MODULEDIR);
+ lua_setglobal(engine->L, "moduledir");
+ lua_pushliteral(engine->L, PREFIX ETCDIR);
+ lua_setglobal(engine->L, "etcdir");
lua_pushlightuserdata(engine->L, engine);
lua_setglobal(engine->L, "__engine");
return kr_ok();
ta.state = key_state.Valid
ta.timer = nil
end
- print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
+ print('[ ta ] key: '..key_tag..' state: '..ta.state)
return true
elseif not key_revoked then -- First time seen (NewKey)
rr.key_tag = key_tag
rr.state = key_state.AddPend
rr.timer = now + hold_down_time
end
- print('[trust_anchors] key: '..key_tag..' state: '..rr.state)
+ print('[ ta ] key: '..key_tag..' state: '..rr.state)
table.insert(keyset, rr)
return true
end
ta.timer = os.time() + hold_down_time
-- Purge pending key
elseif ta.state == key_state.AddPend then
- print('[trust_anchors] key: '..key_tag..' purging')
+ print('[ ta ] key: '..key_tag..' purging')
keep_ta = false
end
- print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
+ print('[ ta ] key: '..key_tag..' state: '..ta.state)
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, priming)
+local function refresh_plan(trust_anchors, timeout, refresh_cb, priming, bootstrap)
trust_anchors.refresh_ev = event.after(timeout, function (ev)
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))
+ local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt), bootstrap)
if trust_anchors.refresh_time ~= nil then
next_time = math.min(next_time, trust_anchors.refresh_time)
end
- print('[trust_anchors] next refresh: '..next_time)
+ print('[ ta ] next refresh: '..next_time)
refresh_plan(trust_anchors, next_time, refresh_cb)
-- Priming query, prime root NS next
if priming ~= nil then
end
-- Active refresh, return time of the next check
-local function active_refresh(trust_anchors, pkt)
+local function active_refresh(trust_anchors, pkt, bootstrap)
local retry = true
if pkt:rcode() == kres.rcode.NOERROR then
local records = pkt:section(kres.section.ANSWER)
table.insert(keyset, rr)
end
end
- trust_anchors.update(keyset, false)
+ trust_anchors.update(keyset, bootstrap)
retry = false
end
-- Calculate refresh/retry timer (RFC 5011, 2.3)
os.rename(path..'.lock', path)
end
+-- Fetch over HTTPS with peert cert checked
+local function https_fetch(url, ca)
+ local https = require('ssl.https')
+ local ltn12 = require('ltn12')
+ local resp = {}
+ local r, c, h, s = https.request{
+ url = url,
+ cafile = ca,
+ verify = {'peer', 'fail_if_no_peer_cert' },
+ protocol = 'tlsv1_2',
+ sink = ltn12.sink.table(resp),
+ }
+ if r == nil then return r, c end
+ return resp[1]
+end
+
-- TA store management
local trust_anchors = {
keyset = {},
return true
end,
-- Load keys from a file (managed)
- config = function (path, is_unmanaged)
- if path == trust_anchors.file_current then return end
+ config = function (path, unmanaged, bootstrap)
+ bootstrap = true
+ -- Bootstrap if requested and keyfile doesn't exist
+ if bootstrap and not io.open(path, 'r') then
+ if not trust_anchors.bootstrap() then
+ error('you MUST obtain the root TA manually, see: '..
+ 'http://knot-resolver.readthedocs.org/en/latest/daemon.html#enabling-dnssec')
+ end
+ elseif path == trust_anchors.file_current then
+ return
+ end
+ -- Parse new keys
local new_keys = require('zonefile').parse_file(path)
trust_anchors.file_current = path
- if is_unmanaged then trust_anchors.file_current = nil end
+ if unmanaged then trust_anchors.file_current = nil end
trust_anchors.keyset = {}
- if trust_anchors.update(new_keys, true) then
+ if bootstrap or trust_anchors.update(new_keys, true) then
if trust_anchors.refresh_ev ~= nil then event.cancel(trust_anchors.refresh_ev) end
- refresh_plan(trust_anchors, sec, active_refresh, true)
+ refresh_plan(trust_anchors, sec, active_refresh, true, bootstrap)
end
end,
-- Add DS/DNSKEY record(s) (unmanaged)
add = function (keystr)
local store = kres.context().trust_anchors
- require('zonefile').parser(function (p)
+ return require('zonefile').parser(function (p)
local rr = p:current_rr()
C.kr_ta_add(store, rr.owner, rr.type, rr.ttl, rr.rdata, #rr.rdata)
end):read(keystr..'\n')
end
trust_anchors.insecure = list
end,
+ bootstrap = function (url, ca)
+ -- Fetch root anchors in XML over HTTPS
+ -- @todo ICANN certificate is verified against current CA
+ -- this is not ideal, as it should rather verify .xml signature which
+ -- is signed by ICANN long-lived cert, but luasec has no PKCS7
+ ca = ca or etcdir..'/icann-ca.pem'
+ url = url or 'https://data.iana.org/root-anchors/root-anchors.xml'
+ local xml, err = https_fetch(url, ca)
+ if not xml then
+ print(string.format('[ ta ] fetch of "%s" failed: %s', url, err))
+ return false
+ end
+ -- Parse root trust anchor
+ local fields = {}
+ string.gsub(xml, "<([%w]+).->([^<]+)</[%w]+>", function (k, v) fields[k] = v end)
+ local rrdata = string.format('%s %s %s %s', fields.KeyDigest, fields.Algorithm, fields.DigestType, fields.Digest)
+ local rr = string.format('%s 0 IN DS %s', fields.TrustAnchor, rrdata)
+ -- Add to key set, create an empty keyset file to be filled
+ if trust_anchors.add(rr) ~= 0 then
+ print(string.format('[ ta ] invalid format of the RR "%s"', rr))
+ return false
+ end
+ print(string.format('[ ta ] bootstrapped root anchor "%s"', rrdata))
+ print('[ ta ] warning: you SHOULD check the key manually, see: '..
+ 'https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs')
+ return true
+ end,
}
return trust_anchors
\ No newline at end of file
#endif
break;
case 'k':
- keyfile_buf = malloc(PATH_MAX + 1);
+ keyfile_buf = malloc(PATH_MAX);
assert(keyfile_buf);
- keyfile = realpath(optarg, keyfile_buf);
- if (keyfile)
- keyfile = strdup(keyfile);
+ /* Check if the path is absolute */
+ if (optarg[0] == '/') {
+ keyfile = strdup(optarg);
+ } else {
+ /* Construct absolute path, the file may not exist */
+ keyfile = realpath(".", keyfile_buf);
+ if (keyfile) {
+ int len = strlen(keyfile);
+ int namelen = strlen(optarg);
+ if (len + namelen < PATH_MAX - 1) {
+ keyfile[len] = '/';
+ memcpy(keyfile + len + 1, optarg, namelen + 1);
+ keyfile = strdup(keyfile); /* Duplicate */
+ } else {
+ keyfile = NULL; /* Invalidate */
+ }
+ }
+ }
free(keyfile_buf);
- if (!keyfile || access(optarg, R_OK|W_OK) != 0) {
- log_error("[system] keyfile '%s': not readable/writeable\n", optarg);
+ if (!keyfile) {
+ log_error("[system] keyfile '%s': not writeable\n", optarg);
return EXIT_FAILURE;
}
break;
.. csv-table::
:header: "Optional", "Needed for", "Notes"
+ "luasocket_", "``trust anchors, modules/stats``", "Sockets for Lua."
+ "luasec_", "``trust anchors``", "TLS for Lua."
"libmemcached_", "``modules/memcached``", "To build memcached backend module."
"hiredis_", "``modules/redis``", "To build redis backend module."
"Go_ 1.5+", "``modules``", "Build modules written in Go."
.. _libknot: https://gitlab.labs.nic.cz/labs/knot
.. _cmocka: https://cmocka.org/
.. _Python: https://www.python.org/
+.. _luasec: https://luarocks.org/modules/luarocks/luasec
+.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
.. _boot2docker: http://boot2docker.io/
--- /dev/null
+etc_SOURCES := icann-ca.pem
+
+etc-install: etcdir
+ $(INSTALL) -m 0640 $(addprefix etc/,$(etc_SOURCES)) $(PREFIX)/$(ETCDIR)
+
+.PHONY: etc-install
--- /dev/null
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 08:3b:e0:56:90:42:46:b1:a1:75:6a:c9:59:91:c7:4a
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
+ Validity
+ Not Before: Nov 10 00:00:00 2006 GMT
+ Not After : Nov 10 00:00:00 2031 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:e2:3b:e1:11:72:de:a8:a4:d3:a3:57:aa:50:a2:
+ 8f:0b:77:90:c9:a2:a5:ee:12:ce:96:5b:01:09:20:
+ cc:01:93:a7:4e:30:b7:53:f7:43:c4:69:00:57:9d:
+ e2:8d:22:dd:87:06:40:00:81:09:ce:ce:1b:83:bf:
+ df:cd:3b:71:46:e2:d6:66:c7:05:b3:76:27:16:8f:
+ 7b:9e:1e:95:7d:ee:b7:48:a3:08:da:d6:af:7a:0c:
+ 39:06:65:7f:4a:5d:1f:bc:17:f8:ab:be:ee:28:d7:
+ 74:7f:7a:78:99:59:85:68:6e:5c:23:32:4b:bf:4e:
+ c0:e8:5a:6d:e3:70:bf:77:10:bf:fc:01:f6:85:d9:
+ a8:44:10:58:32:a9:75:18:d5:d1:a2:be:47:e2:27:
+ 6a:f4:9a:33:f8:49:08:60:8b:d4:5f:b4:3a:84:bf:
+ a1:aa:4a:4c:7d:3e:cf:4f:5f:6c:76:5e:a0:4b:37:
+ 91:9e:dc:22:e6:6d:ce:14:1a:8e:6a:cb:fe:cd:b3:
+ 14:64:17:c7:5b:29:9e:32:bf:f2:ee:fa:d3:0b:42:
+ d4:ab:b7:41:32:da:0c:d4:ef:f8:81:d5:bb:8d:58:
+ 3f:b5:1b:e8:49:28:a2:70:da:31:04:dd:f7:b2:16:
+ f2:4c:0a:4e:07:a8:ed:4a:3d:5e:b5:7f:a3:90:c3:
+ af:27
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
+ X509v3 Authority Key Identifier:
+ keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
+
+ Signature Algorithm: sha1WithRSAEncryption
+ cb:9c:37:aa:48:13:12:0a:fa:dd:44:9c:4f:52:b0:f4:df:ae:
+ 04:f5:79:79:08:a3:24:18:fc:4b:2b:84:c0:2d:b9:d5:c7:fe:
+ f4:c1:1f:58:cb:b8:6d:9c:7a:74:e7:98:29:ab:11:b5:e3:70:
+ a0:a1:cd:4c:88:99:93:8c:91:70:e2:ab:0f:1c:be:93:a9:ff:
+ 63:d5:e4:07:60:d3:a3:bf:9d:5b:09:f1:d5:8e:e3:53:f4:8e:
+ 63:fa:3f:a7:db:b4:66:df:62:66:d6:d1:6e:41:8d:f2:2d:b5:
+ ea:77:4a:9f:9d:58:e2:2b:59:c0:40:23:ed:2d:28:82:45:3e:
+ 79:54:92:26:98:e0:80:48:a8:37:ef:f0:d6:79:60:16:de:ac:
+ e8:0e:cd:6e:ac:44:17:38:2f:49:da:e1:45:3e:2a:b9:36:53:
+ cf:3a:50:06:f7:2e:e8:c4:57:49:6c:61:21:18:d5:04:ad:78:
+ 3c:2c:3a:80:6b:a7:eb:af:15:14:e9:d8:89:c1:b9:38:6c:e2:
+ 91:6c:8a:ff:64:b9:77:25:57:30:c0:1b:24:a3:e1:dc:e9:df:
+ 47:7c:b5:b4:24:08:05:30:ec:2d:bd:0b:bf:45:bf:50:b9:a9:
+ f3:eb:98:01:12:ad:c8:88:c6:98:34:5f:8d:0a:3c:c6:e9:d5:
+ 95:95:6d:de
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----