From 6c2db2b56b5cca96513e2da872d8336743172464 Mon Sep 17 00:00:00 2001 From: Vitezslav Kriz Date: Mon, 14 Aug 2017 09:25:42 +0200 Subject: [PATCH] keyfile argument distiguish managed and unmanaged mode Arguments --keyfile, -k for managed mode and --keyfile-ro, -K for unmanaged (readonly) mode. Automatic setting based on the file permission is removed because it was confusing and could easily lead to state where automatic update does not happen because of unexpected file permissions. Check if folder is writeable was moved into Lua code. Default unmanaged keyfile path can be specified at compile time with option KEYFILE_DEFAULT. This default configuration can be disabled in configuration file with trust_anchors.keyfile_default = nil. --- config.mk | 1 + daemon/README.rst | 2 + daemon/daemon.mk | 3 +- daemon/engine.c | 40 ++++++------ daemon/engine.h | 13 ++-- daemon/lua/config.lua | 10 +++ daemon/lua/trust_anchors.lua.in | 16 ++++- daemon/main.c | 107 ++++++++++++-------------------- doc/build.rst | 2 + doc/kresd.8.in | 20 ++++-- 10 files changed, 111 insertions(+), 103 deletions(-) diff --git a/config.mk b/config.mk index 0023b4b5c..00dd3805c 100644 --- a/config.mk +++ b/config.mk @@ -23,6 +23,7 @@ ROOTHINTS ?= $(ETCDIR)/root.hints COVERAGE_STAGE ?= gcov COVERAGE_STATSDIR ?= $(CURDIR)/coverage.stats TOPSRCDIR := $(CURDIR) +KEYFILE_DEFAULT ?= # Tools CC ?= cc diff --git a/daemon/README.rst b/daemon/README.rst index c79790122..830cc22a3 100644 --- a/daemon/README.rst +++ b/daemon/README.rst @@ -58,6 +58,8 @@ The root anchors bootstrap may fail for various reasons, in this case you need t You've just enabled DNSSEC! +.. note:: Bootstrapping and automatic update need write access to keyfile direcory. If you want to manage root anchors manually you should use ``trust_anchors.add_file('root.keys', true)``. + CLI interface ============= diff --git a/daemon/daemon.mk b/daemon/daemon.mk index a455c2c25..cf28be0ff 100644 --- a/daemon/daemon.mk +++ b/daemon/daemon.mk @@ -56,6 +56,7 @@ daemon-install: kresd-install bindings-install ifneq ($(SED),) $(SED) -e "s/@VERSION@/$(VERSION)/" -e "s/@DATE@/$(date)/" \ -e "s|@MODULEDIR@|$(MODULEDIR)|" \ + -e "s|@KEYFILE_DEFAULT@|$(KEYFILE_DEFAULT)|" \ doc/kresd.8.in > doc/kresd.8 $(INSTALL) -d -m 0755 $(DESTDIR)$(MANDIR)/man8/ $(INSTALL) -m 0644 doc/kresd.8 $(DESTDIR)$(MANDIR)/man8/ @@ -65,7 +66,7 @@ daemon-clean: kresd-clean daemon/lua/zonefile.lua daemon/lua/trust_anchors.lua: daemon/lua/trust_anchors.lua.in - @$(call quiet,SED,$<) -e "s|@ETCDIR@|$(ETCDIR)|g" $< > $@ + @$(call quiet,SED,$<) -e "s|@ETCDIR@|$(ETCDIR)|g;s|@KEYFILE_DEFAULT@|$(KEYFILE_DEFAULT)|g" $< > $@ LIBZSCANNER_COMMENTS := \ $(shell pkg-config libzscanner --atleast-version=2.4.2 && echo true || echo false) diff --git a/daemon/engine.c b/daemon/engine.c index 8eb782442..b7e69db7b 100644 --- a/daemon/engine.c +++ b/daemon/engine.c @@ -812,9 +812,8 @@ int engine_ipc(struct engine *engine, const char *expr) } } -static int engine_loadconf(struct engine *engine, const char *config_path) +int engine_load_sandbox(struct engine *engine) { - int ret = 0; /* Init environment */ static const char sandbox_bytecode[] = { #include "daemon/lua/sandbox.inc" @@ -824,22 +823,13 @@ static int engine_loadconf(struct engine *engine, const char *config_path) lua_pop(engine->L, 1); return kr_error(ENOEXEC); } - /* Load config file */ - if (config_path) { - if (strcmp(config_path, "-") == 0) { - return ret; /* No config and no defaults. */ - } - ret = l_dosandboxfile(engine->L, config_path); - } - if (ret == 0) { - /* Load defaults */ - static const char config_bytecode[] = { - #include "daemon/lua/config.inc" - }; - ret = l_dobytecode(engine->L, config_bytecode, sizeof(config_bytecode), "config"); - } + return kr_ok(); +} - /* Evaluate */ +int engine_loadconf(struct engine *engine, const char *config_path) +{ + assert(config_path != NULL); + int ret = l_dosandboxfile(engine->L, config_path); if (ret != 0) { fprintf(stderr, "%s\n", lua_tostring(engine->L, -1)); lua_pop(engine->L, 1); @@ -847,14 +837,22 @@ static int engine_loadconf(struct engine *engine, const char *config_path) return ret; } -int engine_start(struct engine *engine, const char *config_path) +int engine_load_defaults(struct engine *engine) { - /* Load configuration. */ - int ret = engine_loadconf(engine, config_path); + /* Load defaults */ + static const char config_bytecode[] = { + #include "daemon/lua/config.inc" + }; + int ret = l_dobytecode(engine->L, config_bytecode, sizeof(config_bytecode), "config"); if (ret != 0) { - return ret; + fprintf(stderr, "%s\n", lua_tostring(engine->L, -1)); + lua_pop(engine->L, 1); } + return ret; +} +int engine_start(struct engine *engine) +{ /* Clean up stack and restart GC */ lua_settop(engine->L, 0); lua_gc(engine->L, LUA_GCCOLLECT, 0); diff --git a/daemon/engine.h b/daemon/engine.h index 25ee30a12..dea87c118 100644 --- a/daemon/engine.h +++ b/daemon/engine.h @@ -83,12 +83,13 @@ int engine_pcall(struct lua_State *L, int argc); int engine_ipc(struct engine *engine, const char *expr); -/** Start the lua engine and execute the config. - * - * @note Special path "-" means that even default config won't be done - * (like listening on localhost). - */ -int engine_start(struct engine *engine, const char *config_path); + +int engine_load_sandbox(struct engine *engine); +int engine_loadconf(struct engine *engine, const char *config_path); +int engine_load_defaults(struct engine *engine); + +/** Start the lua engine and execute the config. */ +int engine_start(struct engine *engine); void engine_stop(struct engine *engine); int engine_register(struct engine *engine, const char *name, const char *precedence, const char* ref); int engine_unregister(struct engine *engine, const char *name); diff --git a/daemon/lua/config.lua b/daemon/lua/config.lua index cc6e2f2f2..36f89e672 100644 --- a/daemon/lua/config.lua +++ b/daemon/lua/config.lua @@ -18,3 +18,13 @@ end if kres.context().root_hints.nsset.root == nil then _hint_root_file() end + +if not trust_anchors.keysets['\0'] and trust_anchors.keyfile_default then + if io.open(trust_anchors.keyfile_default, 'r') then + trust_anchors.config(trust_anchors.keyfile_default, true) + else + panic("cannot open default trust anchor file:'%s'", + trust_anchors.keyfile_default + ) + end +end diff --git a/daemon/lua/trust_anchors.lua.in b/daemon/lua/trust_anchors.lua.in index d21a34d2c..d2755b80c 100644 --- a/daemon/lua/trust_anchors.lua.in +++ b/daemon/lua/trust_anchors.lua.in @@ -367,9 +367,16 @@ update = function (keyset, new_keys, is_initial) end local add_file = function (path, unmanaged) - -- Bootstrap if requested and keyfile doesn't exist + if not unmanaged then + if not io.open(path .. '.lock', 'w') then + error("[ ta ] ERROR: write access needed to keyfile dir '"..path.."'") + end + os.remove(path .. ".lock") + end + -- Bootstrap if requested and keyfile doesn't exist if not unmanaged and not io.open(path, 'r') then + log("[ ta ] keyfile '%s': doesn't exist, bootstrapping", path); local tas, msg = bootstrap(trust_anchors.bootstrap_url, trust_anchors.bootstrap_ca) if not tas then msg = msg .. '\n' @@ -394,14 +401,15 @@ local add_file = function (path, unmanaged) end -- Parse the file and check its sanity - local keyset, err = keyset_read(path) - if not unmanaged then keyset.filename = path end + local keyset, err = keyset_read(path) if not keyset then panic("[ ta ] ERROR: failed to read anchors from '%s' (%s)", path, err) end + if not unmanaged then keyset.filename = path end if not keyset[1] then panic("[ ta ] ERROR: failed to read anchors from '%s'", path) end + if not unmanaged then keyset.filename = path end local owner = keyset[1].owner for _, ta in ipairs(keyset) do if ta.owner ~= owner then @@ -446,6 +454,8 @@ trust_anchors = { bootstrap_url = 'https://data.iana.org/root-anchors/root-anchors.xml', bootstrap_ca = '@ETCDIR@/icann-ca.pem', + -- change empty string to nil + keyfile_default = ('@KEYFILE_DEFAULT@' ~= '' and '@KEYFILE_DEFAULT@') or nil, -- Load keys from a file, 5011-managed by default. -- If managed and the file doesn't exist, try bootstrapping the root into it. diff --git a/daemon/main.c b/daemon/main.c index 432900018..0fc44afbb 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -39,6 +39,7 @@ #include "daemon/engine.h" #include "daemon/bindings.h" #include "daemon/tls.h" +#include "lib/dnssec/ta.h" /* We can fork early on Linux 3.9+ and do SO_REUSEPORT for better performance. */ #if defined(UV_VERSION_HEX) && defined(SO_REUSEPORT) && defined(__linux__) @@ -347,7 +348,8 @@ static void help(int argc, char *argv[]) " -S, --fd=[fd] Listen on given fd (handed out by supervisor).\n" " -T, --tlsfd=[fd] Listen using TLS on given fd (handed out by supervisor).\n" " -c, --config=[path] Config file path (relative to [rundir]) (default: config).\n" - " -k, --keyfile=[path] File containing trust anchors (DS or DNSKEY).\n" + " -k, --keyfile=[path] File with root domain trust anchors (DS or DNSKEY), automatically updated.\n" + " -K, --keyfile-ro=[path] File with read-only root domain trust anchors, for use with an external updater.\n" " -m, --moduledir=[path] Override the default module path (" MODULEDIR ").\n" " -f, --forks=N Start N forks sharing the configuration.\n" " -q, --quiet Quiet output, no prompt in interactive mode.\n" @@ -422,79 +424,21 @@ static void free_sd_socket_names(char **socket_names, int count) } #endif -static int init_keyfile(struct engine *engine, const char *keyfile) +static int init_keyfile(struct engine *engine, const char *keyfile, bool keyfile_unmanaged) { - auto_free char *dirname_storage = strdup(keyfile); - if (!dirname_storage) { - return kr_error(ENOMEM); - } - - /* Resolve absolute path to the keyfile directory */ - auto_free char *keyfile_dir = malloc(PATH_MAX); - if (!keyfile_dir) { - return kr_error(ENOMEM); - } - if (realpath(dirname(dirname_storage), keyfile_dir) == NULL) { - kr_log_error("[ ta ]: keyfile '%s' directory: %s\n", keyfile, strerror(errno)); - return kr_error(ENOTDIR); - } - - auto_free char *basename_storage = strdup(keyfile); - if (!basename_storage) { - return kr_error(ENOMEM); - } - - char *_filename = basename(basename_storage); - int dirlen = strlen(keyfile_dir); - int namelen = strlen(_filename); - if (dirlen + 1 + namelen >= PATH_MAX) { - kr_log_error("[ ta ]: keyfile '%s' PATH_MAX exceeded\n", - keyfile); - return kr_error(ENAMETOOLONG); - } - keyfile_dir[dirlen++] = '/'; - keyfile_dir[dirlen] = '\0'; - - auto_free char *keyfile_path = malloc(dirlen + namelen + 1); - if (!keyfile_path) { - return kr_error(ENOMEM); - } - memcpy(keyfile_path, keyfile_dir, dirlen); - memcpy(keyfile_path + dirlen, _filename, namelen + 1); - - int unmanaged = 0; - - /* Note: config has been executed, so access() is OK, - * as we've dropped privileges already if configured. */ - if (access(keyfile_path, F_OK) != 0) { - kr_log_info("[ ta ] keyfile '%s': doesn't exist, bootstrapping\n", keyfile_path); - if (access(keyfile_dir, W_OK) != 0) { - kr_log_error("[ ta ] keyfile '%s': write access to '%s' needed\n", keyfile_path, keyfile_dir); - return kr_error(EPERM); - } - } else if (access(keyfile_path, R_OK) == 0) { - if ((access(keyfile_path, W_OK) != 0) || (access(keyfile_dir, W_OK) != 0)) { - kr_log_error("[ ta ] keyfile '%s': not writeable, starting in unmanaged mode\n", keyfile_path); - unmanaged = 1; - } - } else { - kr_log_error("[ ta ] keyfile '%s': %s\n", keyfile_path, strerror(errno)); - return kr_error(EPERM); - } - - auto_free char *cmd = afmt("trust_anchors.config('%s',%s)", keyfile_path, unmanaged?"true":"nil"); + auto_free char *cmd = afmt("trust_anchors.config('%s',%s)", + keyfile, keyfile_unmanaged ? "true" : "nil"); if (!cmd) { + kr_log_error("[system] not enough memory\n"); return kr_error(ENOMEM); } - int lua_ret = engine_cmd(engine->L, cmd, false); if (lua_ret != 0) { if (lua_gettop(engine->L) > 0) { - kr_log_error("%s", lua_tostring(engine->L, -1)); - lua_settop(engine->L, 0); + kr_log_error("%s\n", lua_tostring(engine->L, -1)); } else { kr_log_error("[ ta ] keyfile '%s': failed to load (%s)\n", - keyfile_path, lua_strerror(lua_ret)); + keyfile, lua_strerror(lua_ret)); } return kr_error(EIO); } @@ -515,6 +459,7 @@ int main(int argc, char **argv) array_t(int) tls_fd_set; array_init(tls_fd_set); char *keyfile = NULL; + int keyfile_unmanaged = 0; char *moduledir = MODULEDIR; const char *config = NULL; int control_fd = -1; @@ -528,6 +473,7 @@ int main(int argc, char **argv) {"tlsfd", required_argument, 0, 'T'}, {"config", required_argument, 0, 'c'}, {"keyfile",required_argument, 0, 'k'}, + {"keyfile-ro",required_argument, 0, 'K'}, {"forks",required_argument, 0, 'f'}, {"moduledir", required_argument, 0, 'm'}, {"verbose", no_argument, 0, 'v'}, @@ -536,7 +482,7 @@ int main(int argc, char **argv) {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - while ((c = getopt_long(argc, argv, "a:t:S:T:c:f:m:k:vqVh", opts, &li)) != -1) { + while ((c = getopt_long(argc, argv, "a:t:S:T:c:f:m:K:k:vqVh", opts, &li)) != -1) { switch (c) { case 'a': @@ -563,7 +509,13 @@ int main(int argc, char **argv) return EXIT_FAILURE; } break; + case 'K': + keyfile_unmanaged = 1; case 'k': + if (keyfile != NULL) { + kr_log_error("[system] error only one of '--keyfile' and '--keyfile-ro' allowed\n"); + return EXIT_FAILURE; + } keyfile = optarg; break; case 'm': @@ -748,14 +700,18 @@ int main(int argc, char **argv) worker->loop = loop; loop->data = worker; - ret = engine_start(&engine, config); + ret = engine_load_sandbox(&engine); + if (ret == 0 && config != NULL && strcmp(config, "-") !=0) { + ret = engine_loadconf(&engine, config); + } + if (ret != 0) { ret = EXIT_FAILURE; goto cleanup; } if (keyfile) { - ret = init_keyfile(&engine, keyfile); + ret = init_keyfile(&engine, keyfile, keyfile_unmanaged); if (ret != 0) { kr_log_error("[system] failed to initialized keyfile: %s\n", kr_strerror(ret)); ret = EXIT_FAILURE; @@ -763,6 +719,21 @@ int main(int argc, char **argv) } } + if (config == NULL || strcmp(config, "-") !=0) { + ret = engine_load_defaults(&engine); + } + + if (ret != 0) { + ret = EXIT_FAILURE; + goto cleanup; + } + + ret = engine_start(&engine); + if (ret != 0) { + ret = EXIT_FAILURE; + goto cleanup; + } + /* Run the event loop */ ret = run_worker(loop, &engine, &ipc_set, fork_id == 0, control_fd); if (ret != 0) { diff --git a/doc/build.rst b/doc/build.rst index 0032ce2bc..6ac99ba75 100644 --- a/doc/build.rst +++ b/doc/build.rst @@ -197,10 +197,12 @@ All paths are prefixed with ``PREFIX`` variable by default if not specified othe "daemon", "``SBINDIR``", "``$(PREFIX)/sbin``", "" "configuration", "``ETCDIR``", "``$(PREFIX)/etc/kresd``", "Configuration file, templates." "modules", "``MODULEDIR``", "``$(LIBDIR)/kdns_modules``", "Runtime directory for loading dynamic modules [#]_." + "trust anchor file", "``KEYFILE_DEFAULT``", "*(none)*", "Path to read-only trust anchor file, which is used as fallback when no other file is specified. [#]_" "work directory", "", "the current directory", "Run directory for daemon. (Only relevant during run time, not e.g. during installation.)" .. [#] The ``libkres.pc`` is installed in ``$(LIBDIR)/pkgconfig``. .. [#] The default moduledir can be changed with `-m` option to `kresd` daemon or by calling `moduledir()` function from lua. +.. [#] If no other trust anchor is specified by user, the compiled-in path ``KEYFILE_DEFAULT`` must contain a valid trust anchor. This is typically used by distributions which provide DNSSEC root trust anchors as part of distribution package. Users can disable the built-in trust anchor by adding ``trust_anchors.keyfile_default = nil`` to their configuration. .. note:: Each module is self-contained and may install additional bundled files within ``$(MODULEDIR)/$(modulename)``. These files should be read-only, non-executable. diff --git a/doc/kresd.8.in b/doc/kresd.8.in index a1be6e24e..eb42bbdd0 100644 --- a/doc/kresd.8.in +++ b/doc/kresd.8.in @@ -24,6 +24,8 @@ .IR config ] .RB [ \-k | \-\-keyfile .IR keyfile ] +.RB [ \-K | \-\-keyfile-ro +.IR keyfile ] .RB [ \-m | \-\-moduledir .IR path ] .RB [ \-f | \-\-forks @@ -121,10 +123,20 @@ file at the default location (\fIconfig\fR). The syntax is described in \fIdaemon/README.md\fR. .TP .B \-k\fI keyfile\fR, \fB\-\-keyfile=\fI -Use given for keeping root trust anchors. If the file doesn't exist, it will be -automatically boostrapped from IANA and warning for you will be issued to check it -before trusting it. The file contains DNSKEY/DS records in presentation format, -and is compatible with Unbound or BIND9 root key files. +(Recommended!) Automatically managed root trust anchors file. +Root trust anchors in this file are managed using standard RFC 5011 (Automated Updates of DNS Security Trust Anchors). +Kresd needs write access to the directory containing the keyfile. + +If the file does not exist, it will be automatically boostrapped from IANA using HTTPS protocol +and warning that you need to to check the key before trusting it will be issued. + +The file contains DNSKEY/DS records in presentation format, +and is compatible with Unbound and BIND 9 root key files. +.TP +.B \-K\fI keyfile\fR, \fB\-\-keyfile-ro=\fI +(Discouraged) Static root trust anchors file. The file is not updated by kresd. Use of this option is discouraged because it will break your installation when the trust anchor key changes! + +Default: "@KEYFILE_DEFAULT@" (can be empty if your distribution did not provide one) .TP .B \-m\fI path\fR, \fB\-\-moduledir=\fI Override the directory that is searched for modules. Default: @MODULEDIR@ -- 2.47.2