kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit 3b20cda11c104eacedb572a739a6b4ffd58b370e
parent 1c33ce01d0326a7c4b4c461ccb30928487c8592f
Author: Joris Vink <joris@coders.se>
Date:   Tue,  7 Sep 2021 21:59:22 +0200

Rework worker startup/privsep config.

Starting with the privsep config, this commit changes the following:

- Removes the root, runas, keymgr_root, keymgr_runas, acme_root and
  acme_runas configuration options.

  Instead these are now configured via a privsep configuration context:

  privsep worker {
      root /tmp
      runas nobody
  }

  This is also configurable via Python using the new kore.privsep() method:

      kore.privsep("worker", root="/tmp", runas="nobody", skip=["chroot"])

Tied into this we also better handle worker startup:

- Per worker process, wait until it signalled it is ready.
- If a worker fails at startup, display its last log lines more clearly.
- Don't start acme process if no domain requires acme.
- Remove each process its individual startup log message in favour
  of a generalized one that displays its PID, root and user.
- At startup, log the kore version and built-ins in a nicer way.
- The worker processes now check things they need to start running
  before signaling they are ready (such as access to CA certs for
  TLS client authentication).

Diffstat:
conf/kore.conf.example | 78++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
include/kore/acme.h | 1+
include/kore/kore.h | 27++++++++++++++++++---------
include/kore/python_methods.h | 3+++
src/acme.c | 26+++++++++++---------------
src/config.c | 258++++++++++++++++++++++++++++++++++++++++---------------------------------------
src/filemap.c | 4++--
src/keymgr.c | 28+++++++++++++---------------
src/kore.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
src/log.c | 28+++++++++++++++++++++++++---
src/python.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/utils.c | 6+-----
src/worker.c | 150++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
13 files changed, 486 insertions(+), 267 deletions(-)

diff --git a/conf/kore.conf.example b/conf/kore.conf.example @@ -20,16 +20,57 @@ server tls { # tls no #} -# The worker process root directory. If chrooting was not disabled -# at startup the worker processes will chroot into this directory. -# -# If this configuration option is not set, Kore will take the current -# working directory as the root. -root /home/joris/src/kore +# Kore can have multiple settings for each processes that run under it. +# There are 3 different type of processes: +# +# 1) Worker processes, these handle the HTTP requests and your code +# runs inside of these. +# 2) The keymgr process, this handles your domain private keys +# and signing during the TLS handshakes. It also holds your +# ACME account-key and will sign ACME requests. +# 3) The acme process, this talks to the ACME servers. +# +# You can individually turn on/off chrooting and dropping user +# privileges per process. The -n and -r command-line options +# are a global override for skipping chroot or dropping user +# permissions on all processes. +# +# If no root/runas options are set in a process, it will inherit the +# default values from the worker processes. +# +# The worker processes will get the current working directory or +# current user if no options where specified for it. +# +# Configures the worker processes. +privsep worker { + # The user the workers will run as. + runas kore + + # The root directory for the worker processes, if chroot isn't + # skipped, this is the directory it will chroot into. + # + # If not set, Kore will take the current working directory. + root /var/chroot/kore + + # We could configure this process to not chroot and only + # chdir into its root directory. + #skip chroot +} -# Worker processes will run as the specified user. If this option is -# missing Kore will run as the current user. -runas joris +# Configures the keymgr process. +# If TLS is enabled you will need to specify paths to the domain +# certificate and key that Kore will load. This loading is done +# from the keymgr (separate process) and all paths must be relative +# to the keymgr process its root configuration option. +privsep keymgr { + # The user the keymgr will run as. + runas keymgr + + # The root directory for the keymgr process. In this example + # we do not turn off chroot for this process so the keymgr + # will chroot into this directory. + root /etc/keymgr +} # How many worker processes Kore will spawn. If the directive # worker_set_affinity is set to 1 (the default) Kore will automatically @@ -88,25 +129,6 @@ workers 4 # NOTE: This file location must be inside your chrooted environment. #rand_file random.data -# Key manager specific options. -# If TLS is enabled you will need to specify paths to the domain -# certificate and key that Kore will load. This loading is done -# from the keymgr (separate process) and all paths must be relative -# to the keymgr_root configuration option. -# -# keymgr_root The root path the keymgr will chdir into. -# If chroot was not disable at startup time -# the keymgr process will chroot into here. -# -# keymgr_runas The user to run the keymgr as. -# -# If privsep and chrooting is enabled at startup time but these -# configuration options were not set, they will take over the -# values from the 'root' and 'runas' configuration options. -# -#keymgr_root -#keymgr_runas - # Filemap settings # filemap_index Name of the file to be used as the directory # index for a filemap. diff --git a/include/kore/acme.h b/include/kore/acme.h @@ -54,6 +54,7 @@ int kore_acme_tls_alpn(SSL *, const unsigned char **, unsigned char *, const unsigned char *, unsigned int, void *); extern char *acme_email; +extern int acme_domains; extern char *acme_provider; #if defined(__cplusplus) diff --git a/include/kore/kore.h b/include/kore/kore.h @@ -450,9 +450,17 @@ struct kore_alog_header { u_int16_t loglen; } __attribute__((packed)); +struct kore_privsep { + char *root; + char *runas; + int skip_runas; + int skip_chroot; +}; + struct kore_worker { u_int16_t id; u_int16_t cpu; + int ready; int running; #if defined(__linux__) int tracing; @@ -464,6 +472,7 @@ struct kore_worker { int restarted; u_int64_t time_locked; struct kore_module_handle *active_hdlr; + struct kore_privsep *ps; /* Used by the workers to store accesslogs. */ struct { @@ -701,8 +710,6 @@ extern int skip_runas; extern int kore_foreground; extern char *kore_pidfile; -extern char *kore_root_path; -extern char *kore_runas_user; extern char *kore_tls_cipher_list; extern volatile sig_atomic_t sig_recv; @@ -711,15 +718,16 @@ extern int tls_version; extern DH *tls_dhparam; extern char *rand_file; extern int keymgr_active; -extern char *keymgr_runas_user; -extern char *keymgr_root_path; -extern char *acme_runas_user; -extern char *acme_root_path; + +extern struct kore_privsep worker_privsep; +extern struct kore_privsep keymgr_privsep; +extern struct kore_privsep acme_privsep; extern u_int8_t nlisteners; extern u_int16_t cpu_count; extern u_int8_t worker_count; extern const char *kore_version; +extern const char *kore_build_date; extern int worker_policy; extern u_int8_t worker_set_affinity; extern u_int32_t worker_rlimit_nofiles; @@ -742,12 +750,13 @@ void kore_proctitle(const char *); void kore_default_getopt(int, char **); void kore_worker_reap(void); -void kore_worker_init(void); +int kore_worker_init(void); +void kore_worker_privsep(void); +void kore_worker_started(void); void kore_worker_make_busy(void); void kore_worker_shutdown(void); void kore_worker_dispatch_signal(int); -void kore_worker_privdrop(const char *, const char *); -void kore_worker_spawn(u_int16_t, u_int16_t, u_int16_t); +int kore_worker_spawn(u_int16_t, u_int16_t, u_int16_t); int kore_worker_keymgr_response_verify(struct kore_msg *, const void *, struct kore_domain **); diff --git a/include/kore/python_methods.h b/include/kore/python_methods.h @@ -62,6 +62,8 @@ static PyObject *python_kore_sendobj(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_server(PyObject *, PyObject *, PyObject *); +static PyObject *python_kore_privsep(PyObject *, PyObject *, + PyObject *); #if defined(KORE_USE_PGSQL) @@ -108,6 +110,7 @@ static struct PyMethodDef pykore_methods[] = { METHOD("domain", python_kore_domain, METH_VARARGS | METH_KEYWORDS), METHOD("server", python_kore_server, METH_VARARGS | METH_KEYWORDS), METHOD("gather", python_kore_gather, METH_VARARGS | METH_KEYWORDS), + METHOD("privsep", python_kore_privsep, METH_VARARGS | METH_KEYWORDS), METHOD("sendobj", python_kore_sendobj, METH_VARARGS | METH_KEYWORDS), METHOD("websocket_broadcast", python_websocket_broadcast, METH_VARARGS), #if defined(KORE_USE_PGSQL) diff --git a/src/acme.c b/src/acme.c @@ -265,10 +265,10 @@ static char *account_url = NULL; static u_int8_t acme_alpn_name[] = { 0xa, 'a', 'c', 'm', 'e', '-', 't', 'l', 's', '/', '1' }; +struct kore_privsep acme_privsep; +int acme_domains = 0; char *acme_email = NULL; char *acme_provider = NULL; -char *acme_root_path = NULL; -char *acme_runas_user = NULL; u_int32_t acme_request_timeout = 8; void @@ -310,7 +310,7 @@ kore_acme_run(void) #if defined(KORE_USE_PYTHON) kore_msg_unregister(KORE_PYTHON_SEND_OBJ); #endif - kore_worker_privdrop(acme_runas_user, acme_root_path); + kore_worker_privsep(); #if defined(__OpenBSD__) if (unveil("/etc/ssl/", "r") == -1) @@ -321,14 +321,10 @@ kore_acme_run(void) http_init(); - if (!kore_quiet) { - kore_log(LOG_NOTICE, - "acme worker started (pid#%d)", worker->pid); - } - LIST_INIT(&orders); LIST_INIT(&signops); + kore_worker_started(); acme_parse_directory(); while (quit != 1) { @@ -434,7 +430,7 @@ kore_acme_tls_challenge_use_cert(SSL *ssl, struct kore_domain *dom) return; } - kore_log(LOG_NOTICE, "[%s] acme-tls/1 challenge requested", + kore_log(LOG_INFO, "[%s] acme-tls/1 challenge requested", dom->domain); if ((c = SSL_get_ex_data(ssl, 0)) == NULL) @@ -989,7 +985,7 @@ acme_order_remove(struct acme_order *order, const char *reason) kore_free(auth); } - kore_log(LOG_NOTICE, "[%s] order removed (%s)", order->domain, reason); + kore_log(LOG_INFO, "[%s] order removed (%s)", order->domain, reason); if (strcmp(reason, "completed")) acme_order_retry(order->domain); @@ -1366,7 +1362,7 @@ cleanup: } else { order->auths--; if (order->auths == 0) { - kore_log(LOG_NOTICE, + kore_log(LOG_INFO, "[%s:auth] authentications done", order->domain); order->state = ACME_ORDER_STATE_RUNNING; } @@ -1389,12 +1385,12 @@ acme_challenge_tls_alpn_01(struct acme_order *order, acme_challenge_tls_alpn_01_create(order, challenge); break; case ACME_STATUS_PROCESSING: - kore_log(LOG_NOTICE, + kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] processing", order->domain); break; case ACME_STATUS_VALID: - kore_log(LOG_NOTICE, + kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] valid", order->domain); ret = KORE_RESULT_OK; @@ -1419,7 +1415,7 @@ acme_challenge_tls_alpn_01_create(struct acme_order *order, u_int8_t digest[SHA256_DIGEST_LENGTH]; if (challenge->flags & ACME_FLAG_CHALLENGE_CREATED) { - kore_log(LOG_NOTICE, + kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] pending keymgr", order->domain); return; @@ -1427,7 +1423,7 @@ acme_challenge_tls_alpn_01_create(struct acme_order *order, challenge->flags |= ACME_FLAG_CHALLENGE_CREATED; - kore_log(LOG_NOTICE, + kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] requested from keymgr", order->domain); diff --git a/src/config.c b/src/config.c @@ -69,8 +69,6 @@ static int configure_file(char *); #if defined(KORE_USE_ACME) static int configure_acme(char *); -static int configure_acme_root(char *); -static int configure_acme_runas(char *); static int configure_acme_email(char *); static int configure_acme_provider(char *); #endif @@ -82,8 +80,7 @@ static int configure_bind(char *); static int configure_bind_unix(char *); static int configure_attach(char *); static int configure_domain(char *); -static int configure_root(char *); -static int configure_runas(char *); +static int configure_privsep(char *); static int configure_workers(char *); static int configure_pidfile(char *); static int configure_rlimit_nofiles(char *); @@ -92,6 +89,9 @@ static int configure_accept_threshold(char *); static int configure_death_policy(char *); static int configure_set_affinity(char *); static int configure_socket_backlog(char *); +static int configure_privsep_skip(char *); +static int configure_privsep_root(char *); +static int configure_privsep_runas(char *); #if defined(KORE_USE_PLATFORM_PLEDGE) static int configure_add_pledge(char *); @@ -103,8 +103,6 @@ static int configure_certkey(char *); static int configure_tls_version(char *); static int configure_tls_cipher(char *); static int configure_tls_dhparam(char *); -static int configure_keymgr_root(char *); -static int configure_keymgr_runas(char *); static int configure_client_verify(char *); static int configure_client_verify_depth(char *); @@ -155,9 +153,6 @@ static int configure_task_threads(char *); #if defined(KORE_USE_PYTHON) static int configure_deployment(char *); -static int configure_skip_chroot(char *); -static int configure_python_path(char *); -static int configure_python_import(char *); #endif #if defined(KORE_USE_CURL) @@ -180,18 +175,18 @@ static struct { { "bind", configure_bind }, { "load", configure_load }, { "domain", configure_domain }, + { "privsep", configure_privsep }, { "server", configure_server }, { "attach", configure_attach }, { "certkey", configure_certkey }, { "certfile", configure_certfile }, { "include", configure_include }, { "unix", configure_bind_unix }, + { "skip", configure_privsep_skip }, + { "root", configure_privsep_root }, + { "runas", configure_privsep_runas }, { "client_verify", configure_client_verify }, { "client_verify_depth", configure_client_verify_depth }, -#if defined(KORE_USE_PYTHON) - { "python_path", configure_python_path }, - { "python_import", configure_python_import }, -#endif #if !defined(KORE_NO_HTTP) { "route", configure_route}, { "filemap", configure_filemap }, @@ -217,9 +212,6 @@ static struct { const char *name; int (*configure)(char *); } config_settings[] = { - { "root", configure_root }, - { "chroot", configure_root }, - { "runas", configure_runas }, { "workers", configure_workers }, { "worker_max_connections", configure_max_connections }, { "worker_rlimit_nofiles", configure_rlimit_nofiles }, @@ -232,11 +224,7 @@ static struct { { "tls_cipher", configure_tls_cipher }, { "tls_dhparam", configure_tls_dhparam }, { "rand_file", configure_rand_file }, - { "keymgr_runas", configure_keymgr_runas }, - { "keymgr_root", configure_keymgr_root }, #if defined(KORE_USE_ACME) - { "acme_runas", configure_acme_runas }, - { "acme_root", configure_acme_root }, { "acme_email", configure_acme_email }, { "acme_provider", configure_acme_provider }, #endif @@ -267,7 +255,6 @@ static struct { #endif #if defined(KORE_USE_PYTHON) { "deployment", configure_deployment }, - { "skipchroot", configure_skip_chroot }, #endif #if defined(KORE_USE_PGSQL) { "pgsql_conn_max", configure_pgsql_conn_max }, @@ -302,12 +289,14 @@ static struct kore_module_handle *current_handler = NULL; extern const char *__progname; static struct kore_domain *current_domain = NULL; static struct kore_server *current_server = NULL; +static struct kore_privsep *current_privsep = NULL; void kore_parse_config(void) { FILE *fp; BIO *bio; + struct passwd *pwd; char path[PATH_MAX]; if (finalized) @@ -345,10 +334,10 @@ kore_parse_config(void) if (!kore_module_loaded()) fatal("no application module was loaded"); - if (kore_root_path == NULL) { + if (worker_privsep.root == NULL) { if (getcwd(path, sizeof(path)) == NULL) fatal("getcwd: %s", errno_s); - kore_root_path = kore_strdup(path); + worker_privsep.root = kore_strdup(path); if (!kore_quiet) { kore_log(LOG_NOTICE, "privsep: no root path set, " @@ -356,37 +345,54 @@ kore_parse_config(void) } } - if (getuid() != 0 && skip_chroot == 0) - fatal("cannot chroot, use -n to skip it"); + if (worker_privsep.runas == NULL) { + if ((pwd = getpwuid(getuid())) == NULL) + fatal("getpwuid: %s", errno_s); - if (skip_runas != 1 && kore_runas_user == NULL) - fatal("missing runas user, use -r to skip it"); + worker_privsep.runas = kore_strdup(pwd->pw_name); + if (!kore_quiet) { + kore_log(LOG_NOTICE, "privsep: no runas user set, " + "using current user %s", worker_privsep.runas); + } - if (getuid() != 0 && skip_runas == 0) - fatal("cannot drop privileges, use -r to skip it"); + endpwent(); + } - if (skip_runas) { - if (!kore_quiet) - kore_log(LOG_WARNING, "privsep: will not change user"); - } else { - configure_check_var(&keymgr_runas_user, kore_runas_user, - "privsep: no keymgr_runas set, using 'runas` user"); + configure_check_var(&keymgr_privsep.runas, worker_privsep.runas, + "privsep: no keymgr runas set, using 'privsep.worker.runas`"); #if defined(KORE_USE_ACME) - configure_check_var(&acme_runas_user, kore_runas_user, - "privsep: no acme_runas set, using 'runas` user"); + configure_check_var(&acme_privsep.runas, worker_privsep.runas, + "privsep: no acme runas set, using 'privsep.worker.runas`"); +#endif + + configure_check_var(&keymgr_privsep.root, worker_privsep.root, + "privsep: no keymgr root set, using 'privsep.worker.root`"); +#if defined(KORE_USE_ACME) + configure_check_var(&acme_privsep.root, worker_privsep.root, + "privsep: no acme root set, using 'privsep.worker.root`"); #endif - } - configure_check_var(&keymgr_root_path, kore_root_path, - "privsep: no keymgr_root set, using 'root` directory"); + if (skip_chroot) { + worker_privsep.skip_chroot = 1; + keymgr_privsep.skip_chroot = 1; +#if defined(KORE_USE_ACME) + acme_privsep.skip_chroot = 1; +#endif + } + if (skip_runas) { + worker_privsep.skip_runas = 1; + keymgr_privsep.skip_runas = 1; #if defined(KORE_USE_ACME) - configure_check_var(&acme_root_path, kore_root_path, - "privsep: no acme_root set, using 'root` directory"); + acme_privsep.skip_runas = 1; #endif + } + + if (skip_runas && !kore_quiet) + kore_log(LOG_NOTICE, "privsep: skipping all runas options"); if (skip_chroot && !kore_quiet) - kore_log(LOG_WARNING, "privsep: will not chroot"); + kore_log(LOG_NOTICE, "privsep: skipping all chroot options"); finalized = 1; } @@ -404,6 +410,12 @@ kore_parse_config_file(FILE *fp) continue; } + if (!strcmp(p, "}") && current_privsep != NULL) { + lineno++; + current_privsep = NULL; + continue; + } + if (!strcmp(p, "}") && current_server != NULL) { lineno++; kore_server_finalize(current_server); @@ -649,7 +661,7 @@ configure_acme(char *yesno) kore_acme_get_paths(current_domain->domain, &current_domain->certkey, &current_domain->certfile); - + acme_domains++; } else { printf("invalid '%s' for yes|no acme option\n", yesno); return (KORE_RESULT_ERROR); @@ -659,24 +671,6 @@ configure_acme(char *yesno) } static int -configure_acme_runas(char *user) -{ - kore_free(acme_runas_user); - acme_runas_user = kore_strdup(user); - - return (KORE_RESULT_OK); -} - -static int -configure_acme_root(char *root) -{ - kore_free(acme_root_path); - acme_root_path = kore_strdup(root); - - return (KORE_RESULT_OK); -} - -static int configure_acme_email(char *email) { kore_free(acme_email); @@ -937,21 +931,89 @@ configure_certkey(char *path) } static int -configure_keymgr_runas(char *user) +configure_privsep(char *options) +{ + char *argv[3]; + + if (current_privsep != NULL) { + printf("nested privsep contexts are not allowed\n"); + return (KORE_RESULT_ERROR); + } + + kore_split_string(options, " ", argv, 3); + + if (argv[0] == NULL || argv[1] == NULL) { + printf("invalid privsep context\n"); + return (KORE_RESULT_ERROR); + } + + if (strcmp(argv[1], "{")) { + printf("privsep context not opened correctly\n"); + return (KORE_RESULT_ERROR); + } + + if (!strcmp(argv[0], "worker")) { + current_privsep = &worker_privsep; + } else if (!strcmp(argv[0], "keymgr")) { + current_privsep = &keymgr_privsep; +#if defined(KORE_USE_ACME) + } else if (!strcmp(argv[0], "keymgr")) { + current_privsep = &acme_privsep; +#endif + } else { + printf("unknown privsep context: %s\n", argv[0]); + return (KORE_RESULT_ERROR); + } + + return (KORE_RESULT_OK); +} + +static int +configure_privsep_runas(char *user) { - if (keymgr_runas_user != NULL) - kore_free(keymgr_runas_user); - keymgr_runas_user = kore_strdup(user); + if (current_privsep == NULL) { + printf("runas not specified in privsep context\n"); + return (KORE_RESULT_ERROR); + } + + if (current_privsep->runas != NULL) + kore_free(current_privsep->runas); + + current_privsep->runas = kore_strdup(user); + + return (KORE_RESULT_OK); +} + +static int +configure_privsep_root(char *root) +{ + if (current_privsep == NULL) { + printf("root not specified in privsep context\n"); + return (KORE_RESULT_ERROR); + } + + if (current_privsep->root != NULL) + kore_free(current_privsep->root); + + current_privsep->root = kore_strdup(root); return (KORE_RESULT_OK); } static int -configure_keymgr_root(char *root) +configure_privsep_skip(char *option) { - if (keymgr_root_path != NULL) - kore_free(keymgr_root_path); - keymgr_root_path = kore_strdup(root); + if (current_privsep == NULL) { + printf("skip not specified in privsep context\n"); + return (KORE_RESULT_ERROR); + } + + if (!strcmp(option, "chroot")) { + current_privsep->skip_chroot = 1; + } else { + printf("unknown skip option '%s'\n", option); + return (KORE_RESULT_ERROR); + } return (KORE_RESULT_OK); } @@ -1710,26 +1772,6 @@ configure_websocket_timeout(char *option) #endif /* !KORE_NO_HTTP */ static int -configure_root(char *path) -{ - if (kore_root_path != NULL) - kore_free(kore_root_path); - kore_root_path = kore_strdup(path); - - return (KORE_RESULT_OK); -} - -static int -configure_runas(char *user) -{ - if (kore_runas_user != NULL) - kore_free(kore_runas_user); - kore_runas_user = kore_strdup(user); - - return (KORE_RESULT_OK); -} - -static int configure_workers(char *option) { int err; @@ -1886,22 +1928,6 @@ configure_task_threads(char *option) #if defined(KORE_USE_PYTHON) static int -configure_skip_chroot(char *value) -{ - if (!strcmp(value, "True")) { - skip_chroot = 1; - } else if (!strcmp(value, "False")) { - skip_chroot = 0; - } else { - kore_log(LOG_NOTICE, - "kore.config.skipchroot: bad value '%s'", value); - return (KORE_RESULT_ERROR); - } - - return (KORE_RESULT_OK); -} - -static int configure_deployment(char *value) { if (!strcmp(value, "docker")) { @@ -1925,26 +1951,6 @@ configure_deployment(char *value) return (KORE_RESULT_OK); } -static int -configure_python_path(char *path) -{ - kore_python_path(path); - - return (KORE_RESULT_OK); -} - -static int -configure_python_import(char *module) -{ - char *argv[3]; - - kore_split_string(module, " ", argv, 3); - if (argv[0] == NULL) - return (KORE_RESULT_ERROR); - - kore_module_load(argv[0], argv[1], KORE_MODULE_PYTHON); - return (KORE_RESULT_OK); -} #endif #if defined(KORE_USE_PLATFORM_PLEDGE) diff --git a/src/filemap.c b/src/filemap.c @@ -69,9 +69,9 @@ kore_filemap_create(struct kore_domain *dom, const char *path, const char *root) if (root[0] != '/' || root[sz - 1] != '/') return (KORE_RESULT_ERROR); - if (kore_root_path != NULL) { + if (worker_privsep.root != NULL) { len = snprintf(fpath, sizeof(fpath), "%s/%s", - kore_root_path, path); + worker_privsep.root, path); if (len == -1 || (size_t)len >= sizeof(fpath)) fatal("kore_filemap_create: failed to concat paths"); } else { diff --git a/src/keymgr.c b/src/keymgr.c @@ -248,9 +248,8 @@ static void keymgr_rsa_encrypt(struct kore_msg *, const void *, static void keymgr_ecdsa_sign(struct kore_msg *, const void *, struct key *); -int keymgr_active = 0; -char *keymgr_root_path = NULL; -char *keymgr_runas_user = NULL; +struct kore_privsep keymgr_privsep; +int keymgr_active = 0; #if defined(__OpenBSD__) #if defined(KORE_USE_ACME) @@ -293,10 +292,7 @@ kore_keymgr_run(void) #if defined(KORE_USE_PYTHON) kore_msg_unregister(KORE_PYTHON_SEND_OBJ); #endif - kore_worker_privdrop(keymgr_runas_user, keymgr_root_path); - - if (!kore_quiet) - kore_log(LOG_NOTICE, "key manager started (pid#%d)", getpid()); + kore_worker_privsep(); if (rand_file != NULL) { keymgr_load_randfile(); @@ -321,6 +317,8 @@ kore_keymgr_run(void) X509V3_EXT_add_alias(acme_oid, NID_subject_key_identifier); #endif + kore_worker_started(); + while (quit != 1) { now = kore_time_ms(); if ((now - last_seed) > RAND_POLL_INTERVAL) { @@ -367,7 +365,7 @@ kore_keymgr_cleanup(int final) struct key *key, *next; if (final && !kore_quiet) - kore_log(LOG_NOTICE, "cleaning up keys"); + kore_log(LOG_INFO, "cleaning up keys"); if (initialized == 0) return; @@ -788,7 +786,7 @@ keymgr_acme_init(void) ACME_RENEWAL_TIMER, NULL, 0); if (key->pkey == NULL) { - kore_log(LOG_NOTICE, "generating new ACME account key"); + kore_log(LOG_INFO, "generating new ACME account key"); key->pkey = kore_rsakey_generate(KORE_ACME_ACCOUNT_KEY); needsreg = 1; } else { @@ -828,7 +826,7 @@ keymgr_acme_domainkey(struct kore_domain *dom, struct key *key) { char *p; - kore_log(LOG_NOTICE, "generated new domain key for %s", dom->domain); + kore_log(LOG_INFO, "generated new domain key for %s", dom->domain); if ((p = strrchr(dom->certkey, '/')) == NULL) fatalx("invalid certkey path '%s'", dom->certkey); @@ -1029,7 +1027,7 @@ keymgr_acme_install_cert(const void *data, size_t len, struct key *key) fd = open(key->dom->certfile, O_CREAT | O_TRUNC | O_WRONLY, 0700); if (fd == -1) - fatal("open(%s): %s", key->dom->certfile, errno_s); + fatalx("open(%s): %s", key->dom->certfile, errno_s); kore_log(LOG_INFO, "writing %zu bytes of data", len); @@ -1038,14 +1036,14 @@ keymgr_acme_install_cert(const void *data, size_t len, struct key *key) if (ret == -1) { if (errno == EINTR) continue; - fatal("write(%s): %s", key->dom->certfile, errno_s); + fatalx("write(%s): %s", key->dom->certfile, errno_s); } break; } if ((size_t)ret != len) { - fatal("incorrect write on %s (%zd/%zu)", + fatalx("incorrect write on %s (%zd/%zu)", key->dom->certfile, ret, len); } @@ -1102,7 +1100,7 @@ keymgr_acme_challenge_cert(const void *data, size_t len, struct key *key) slen = snprintf(hex + (idx * 2), sizeof(hex) - (idx * 2), "%02x", digest[idx]); if (slen == -1 || (size_t)slen >= sizeof(hex)) - fatal("failed to convert digest to hex"); + fatalx("failed to convert digest to hex"); } if ((x509 = X509_new()) == NULL) @@ -1176,7 +1174,7 @@ keymgr_acme_csr(const struct kore_keyreq *req, struct key *key) kore_log(LOG_INFO, "[%s] creating CSR", req->domain); if ((csr = X509_REQ_new()) == NULL) - fatal("X509_REQ_new: %s", ssl_errno_s); + fatalx("X509_REQ_new: %s", ssl_errno_s); if (!X509_REQ_set_version(csr, 3)) fatalx("X509_REQ_set_version(): %s", ssl_errno_s); diff --git a/src/kore.c b/src/kore.c @@ -64,12 +64,12 @@ u_int8_t worker_count = 0; char **kore_argv = NULL; int kore_foreground = 0; char *kore_progname = NULL; -char *kore_root_path = NULL; -char *kore_runas_user = NULL; u_int32_t kore_socket_backlog = 5000; char *kore_pidfile = KORE_PIDFILE_DEFAULT; char *kore_tls_cipher_list = KORE_DEFAULT_CIPHER_LIST; +struct kore_privsep worker_privsep; + extern char **environ; extern char *__progname; static size_t proctitle_maxlen = 0; @@ -113,9 +113,9 @@ usage(void) #endif printf("\t-f\tstart in foreground\n"); printf("\t-h\tthis help text\n"); - printf("\t-n\tdo not chroot\n"); + printf("\t-n\tdo not chroot on any worker\n"); printf("\t-q\tonly log errors\n"); - printf("\t-r\tdo not drop privileges\n"); + printf("\t-r\tdo not change user on any worker\n"); printf("\t-v\tdisplay %s build information\n", __progname); printf("\nFind more information on https://kore.io\n"); @@ -226,6 +226,34 @@ main(int argc, char *argv[]) kore_module_init(); kore_server_sslstart(); + if (!kore_quiet) { + kore_log(LOG_INFO, "%s %s starting, built=%s", + __progname, kore_version, kore_build_date); + kore_log(LOG_INFO, "built-ins: " +#if defined(__linux__) + "seccomp " +#endif +#if defined(KORE_USE_PGSQL) + "pgsql " +#endif +#if defined(KORE_USE_TASKS) + "tasks " +#endif +#if defined(KORE_USE_JSONRPC) + "jsonrpc " +#endif +#if defined(KORE_USE_PYTHON) + "python " +#endif +#if defined(KORE_USE_ACME) + "acme " +#endif +#if defined(KORE_USE_CURL) + "curl " +#endif + ); + } + #if !defined(KORE_SINGLE_BINARY) && !defined(KORE_USE_PYTHON) if (config_file == NULL) usage(); @@ -276,7 +304,7 @@ main(int argc, char *argv[]) kore_server_start(argc, argv); if (!kore_quiet) - kore_log(LOG_NOTICE, "server shutting down"); + kore_log(LOG_INFO, "server shutting down"); kore_worker_shutdown(); @@ -292,7 +320,7 @@ main(int argc, char *argv[]) kore_server_cleanup(); if (!kore_quiet) - kore_log(LOG_NOTICE, "goodbye"); + kore_log(LOG_INFO, "goodbye"); #if defined(KORE_USE_PYTHON) kore_python_cleanup(); @@ -554,10 +582,10 @@ kore_server_finalize(struct kore_server *srv) proto = "http"; if (l->family == AF_UNIX) { - kore_log(LOG_NOTICE, "%s serving %s on %s", + kore_log(LOG_INFO, "%s serving %s on %s", srv->name, proto, l->host); } else { - kore_log(LOG_NOTICE, "%s serving %s on %s:%s", + kore_log(LOG_INFO, "%s serving %s on %s:%s", srv->name, proto, l->host, l->port); } } @@ -881,25 +909,6 @@ kore_server_start(int argc, char *argv[]) kore_pid = getpid(); kore_write_kore_pid(); - if (!kore_quiet) { - kore_log(LOG_NOTICE, "%s is starting up", __progname); -#if defined(__linux__) - kore_log(LOG_NOTICE, "seccomp sandbox enabled"); -#endif -#if defined(KORE_USE_PGSQL) - kore_log(LOG_NOTICE, "pgsql built-in enabled"); -#endif -#if defined(KORE_USE_TASKS) - kore_log(LOG_NOTICE, "tasks built-in enabled"); -#endif -#if defined(KORE_USE_JSONRPC) - kore_log(LOG_NOTICE, "jsonrpc built-in enabled"); -#endif -#if defined(KORE_USE_PYTHON) - kore_log(LOG_NOTICE, "python built-in enabled"); -#endif - } - #if !defined(KORE_SINGLE_BINARY) && !defined(KORE_USE_PYTHON) kore_call_parent_configure(argc, argv); #endif @@ -922,7 +931,19 @@ kore_server_start(int argc, char *argv[]) } kore_platform_proctitle("[parent]"); - kore_worker_init(); + + if (!kore_worker_init()) { + kore_log(LOG_ERR, "last worker log lines:"); + kore_log(LOG_ERR, "====================================="); + net_init(); + kore_connection_init(); + kore_platform_event_init(); + kore_msg_parent_init(); + kore_platform_event_wait(10); + kore_worker_dispatch_signal(SIGQUIT); + kore_log(LOG_ERR, "====================================="); + return; + } /* Set worker_max_connections for kore_connection_init(). */ tmp = worker_max_connections; diff --git a/src/log.c b/src/log.c @@ -27,6 +27,7 @@ struct kore_wlog { char logmsg[]; }; +static void log_print(int, const char *, ...); static void log_from_worker(struct kore_msg *, const void *); void @@ -79,7 +80,7 @@ kore_log(int prio, const char *fmt, ...) str = kore_buf_stringify(&buf, NULL); if (kore_foreground) - printf("[parent]: %s\n", str); + log_print(prio, "[parent]: %s\n", str); else syslog(prio, "[parent]: %s", str); } @@ -104,11 +105,32 @@ log_from_worker(struct kore_msg *msg, const void *data) name = kore_worker_name(wlog->wid); if (kore_foreground) { - printf("%s: %.*s\n", + log_print(wlog->prio, "%s: %.*s\n", name, (int)wlog->loglen, wlog->logmsg); - fflush(stdout); } else { syslog(wlog->prio, "%s: %.*s", name, (int)wlog->loglen, wlog->logmsg); } } + +static void +log_print(int prio, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + switch (prio) { + case LOG_ERR: + case LOG_WARNING: + case LOG_NOTICE: + case LOG_INFO: + case LOG_DEBUG: + break; + } + + vprintf(fmt, args); + fflush(stdout); + + va_end(args); +} diff --git a/src/python.c b/src/python.c @@ -1848,6 +1848,73 @@ python_kore_server(PyObject *self, PyObject *args, PyObject *kwargs) } static PyObject * +python_kore_privsep(PyObject *self, PyObject *args, PyObject *kwargs) +{ + struct kore_privsep *ps; + const char *val; + PyObject *skip, *obj; + Py_ssize_t list_len, idx; + + if (!PyArg_ParseTuple(args, "s", &val)) + return (NULL); + + if (!strcmp(val, "worker")) { + ps = &worker_privsep; + } else if (!strcmp(val, "keymgr")) { + ps = &keymgr_privsep; +#if defined(KORE_USE_ACME) + } else if (!strcmp(val, "keymgr")) { + ps = &acme_privsep; +#endif + } else { + PyErr_Format(PyExc_RuntimeError, + "unknown privsep process '%s'", val); + return (NULL); + } + + if ((val = python_string_from_dict(kwargs, "root")) != NULL) { + kore_free(ps->root); + ps->root = kore_strdup(val); + } + + if ((val = python_string_from_dict(kwargs, "runas")) != NULL) { + kore_free(ps->runas); + ps->runas = kore_strdup(val); + } + + if ((skip = PyDict_GetItemString(kwargs, "skip")) != NULL) { + if (!PyList_CheckExact(skip)) { + PyErr_Format(PyExc_RuntimeError, + "privsep skip keyword needs to be a list"); + return (NULL); + } + + list_len = PyList_Size(skip); + + for (idx = 0; idx < list_len; idx++) { + if ((obj = PyList_GetItem(skip, idx)) == NULL) + return (NULL); + + if (!PyUnicode_Check(obj)) + return (NULL); + + if ((val = PyUnicode_AsUTF8AndSize(obj, NULL)) == NULL) + return (NULL); + + if (!strcmp(val, "chroot")) { + ps->skip_chroot = 1; + } else { + PyErr_Format(PyExc_RuntimeError, + "unknown skip keyword '%s'", val); + return (NULL); + } + } + } + + Py_RETURN_NONE; +} + +static PyObject * python_kore_prerequest(PyObject *self, PyObject *args) { PyObject *f; diff --git a/src/utils.c b/src/utils.c @@ -652,14 +652,10 @@ fatal_log(const char *fmt, va_list args) extern const char *kore_progname; (void)vsnprintf(buf, sizeof(buf), fmt, args); - - if (!kore_foreground) - kore_log(LOG_ERR, "%s", buf); + kore_log(LOG_ERR, "%s", buf); if (worker != NULL && worker->id == KORE_WORKER_KEYMGR) kore_keymgr_cleanup(1); - - printf("%s: %s\n", kore_progname, buf); } static int diff --git a/src/worker.c b/src/worker.c @@ -16,6 +16,7 @@ #include <sys/param.h> #include <sys/types.h> +#include <sys/stat.h> #include <sys/shm.h> #include <sys/wait.h> #include <sys/time.h> @@ -28,6 +29,7 @@ #include <grp.h> #include <pwd.h> #include <signal.h> +#include <unistd.h> #include "kore.h" @@ -77,6 +79,7 @@ struct wlock { static int worker_trylock(void); static void worker_unlock(void); static void worker_reaper(pid_t, int); +static void worker_domain_check(struct kore_domain *); static inline int worker_acceptlock_obtain(void); static inline void worker_acceptlock_release(void); @@ -99,7 +102,7 @@ u_int32_t worker_max_connections = 512; u_int32_t worker_active_connections = 0; int worker_policy = KORE_WORKER_POLICY_RESTART; -void +int kore_worker_init(void) { size_t len; @@ -149,27 +152,33 @@ kore_worker_init(void) for (idx = KORE_WORKER_BASE; idx < worker_count; idx++) { if (cpu >= cpu_count) cpu = 0; - kore_worker_spawn(idx, id++, cpu++); + if (!kore_worker_spawn(idx, id++, cpu++)) + return (KORE_RESULT_ERROR); } if (keymgr_active) { #if defined(KORE_USE_ACME) /* The ACME process is only started if we need it. */ - if (acme_provider) { - kore_worker_spawn(KORE_WORKER_ACME_IDX, - KORE_WORKER_ACME, 0); + if (acme_domains) { + if (!kore_worker_spawn(KORE_WORKER_ACME_IDX, + KORE_WORKER_ACME, 0)) + return (KORE_RESULT_ERROR); } #endif /* Now we can start the keymgr. */ - kore_worker_spawn(KORE_WORKER_KEYMGR_IDX, - KORE_WORKER_KEYMGR, 0); + if (!kore_worker_spawn(KORE_WORKER_KEYMGR_IDX, + KORE_WORKER_KEYMGR, 0)) + return (KORE_RESULT_ERROR); } + + return (KORE_RESULT_OK); } -void +int kore_worker_spawn(u_int16_t idx, u_int16_t id, u_int16_t cpu) { + int cnt; struct kore_worker *kw; kw = WORKER(idx); @@ -178,6 +187,7 @@ kore_worker_spawn(u_int16_t idx, u_int16_t id, u_int16_t cpu) kw->has_lock = 0; kw->active_hdlr = NULL; kw->running = 1; + kw->ready = 0; if (socketpair(AF_UNIX, SOCK_STREAM, 0, kw->pipe) == -1) fatal("socketpair(): %s", errno_s); @@ -186,6 +196,20 @@ kore_worker_spawn(u_int16_t idx, u_int16_t id, u_int16_t cpu) !kore_connection_nonblock(kw->pipe[1], 0)) fatal("could not set pipe fds to nonblocking: %s", errno_s); + switch (id) { + case KORE_WORKER_KEYMGR: + kw->ps = &keymgr_privsep; + break; +#if defined(KORE_USE_ACME) + case KORE_WORKER_ACME: + kw->ps = &acme_privsep; + break; +#endif + default: + kw->ps = &worker_privsep; + break; + } + kw->pid = fork(); if (kw->pid == -1) fatal("could not spawn worker child: %s", errno_s); @@ -193,8 +217,24 @@ kore_worker_spawn(u_int16_t idx, u_int16_t id, u_int16_t cpu) if (kw->pid == 0) { kw->pid = getpid(); kore_worker_entry(kw); - /* NOTREACHED */ + exit(1); + } else { + for (cnt = 0; cnt < 25; cnt++) { + if (kw->ready == 1) + break; + usleep(10000); + } + + if (kw->ready == 0) { + kore_log(LOG_NOTICE, + "worker %d failed to start, shutting down", + kw->id); + + return (KORE_RESULT_ERROR); + } } + + return (KORE_RESULT_OK); } struct kore_worker * @@ -282,37 +322,43 @@ kore_worker_dispatch_signal(int sig) } void -kore_worker_privdrop(const char *runas, const char *root) +kore_worker_privsep(void) { rlim_t fd; struct rlimit rl; - struct passwd *pw = NULL; + struct passwd *pw; - if (root == NULL) - fatalx("no root directory for kore_worker_privdrop"); + if (worker == NULL) + fatalx("%s called with no worker", __func__); + + pw = NULL; /* Must happen before chroot. */ - if (skip_runas == 0) { - if (runas == NULL) - fatalx("no runas user given and -r not specified"); - pw = getpwnam(runas); - if (pw == NULL) { + if (worker->ps->skip_runas == 0) { + if (worker->ps->runas == NULL) { + fatalx("no runas user given for %s", + kore_worker_name(worker->id)); + } + + if ((pw = getpwnam(worker->ps->runas)) == NULL) { fatalx("cannot getpwnam(\"%s\") for user: %s", - runas, errno_s); + worker->ps->runas, errno_s); } } - if (skip_chroot == 0) { - if (chroot(root) == -1) { + if (worker->ps->skip_chroot == 0) { + if (chroot(worker->ps->root) == -1) { fatalx("cannot chroot(\"%s\"): %s", - root, errno_s); + worker->ps->root, errno_s); } if (chdir("/") == -1) fatalx("cannot chdir(\"/\"): %s", errno_s); } else { - if (chdir(root) == -1) - fatalx("cannot chdir(\"%s\"): %s", root, errno_s); + if (chdir(worker->ps->root) == -1) { + fatalx("cannot chdir(\"%s\"): %s", + worker->ps->root, errno_s); + } } if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { @@ -332,7 +378,7 @@ kore_worker_privdrop(const char *runas, const char *root) worker_rlimit_nofiles, errno_s); } - if (skip_runas == 0) { + if (worker->ps->skip_runas == 0) { if (setgroups(1, &pw->pw_gid) || #if defined(__MACH__) || defined(NetBSD) setgid(pw->pw_gid) || setegid(pw->pw_gid) || @@ -341,7 +387,7 @@ kore_worker_privdrop(const char *runas, const char *root) setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) #endif - fatalx("cannot drop privileges"); + fatalx("cannot drop privileges (%s)", errno_s); } kore_platform_sandbox(); @@ -370,7 +416,6 @@ kore_worker_entry(struct kore_worker *kw) kore_platform_worker_setcpu(kw); kore_pid = kw->pid; - kore_signal_setup(); if (kw->id == KORE_WORKER_KEYMGR) { @@ -394,7 +439,7 @@ kore_worker_entry(struct kore_worker *kw) kore_task_init(); #endif - kore_worker_privdrop(kore_runas_user, kore_root_path); + kore_worker_privsep(); #if !defined(KORE_NO_HTTP) http_init(); @@ -435,12 +480,6 @@ kore_worker_entry(struct kore_worker *kw) if (nlisteners == 0) worker_no_lock = 1; - if (!kore_quiet) { - kore_log(LOG_NOTICE, - "worker %d started (cpu#%d, pid#%d)", - kw->id, kw->cpu, kw->pid); - } - rcall = kore_runtime_getcall("kore_worker_configure"); if (rcall != NULL) { kore_runtime_execute(rcall); @@ -448,6 +487,9 @@ kore_worker_entry(struct kore_worker *kw) } kore_module_onload(); + kore_domain_callback(worker_domain_check); + + kore_worker_started(); worker->restarted = 0; for (;;) { @@ -672,6 +714,40 @@ kore_worker_keymgr_response_verify(struct kore_msg *msg, const void *data, return (KORE_RESULT_OK); } +void +kore_worker_started(void) +{ + const char *chroot; + + if (worker->ps->skip_chroot) + chroot = "root"; + else + chroot = "chroot"; + + if (!kore_quiet) { + kore_log(LOG_NOTICE, + "process started (#%d %s=%s%s%s)", + getpid(), chroot, worker->ps->root, + worker->ps->skip_runas ? "" : " user=", + worker->ps->skip_runas ? "" : worker->ps->runas); + } + + worker->ready = 1; +} + +static void +worker_domain_check(struct kore_domain *dom) +{ + struct stat st; + + if (dom->cafile != NULL) { + if (stat(dom->cafile, &st) == -1) + fatalx("'%s': %s", dom->cafile, errno_s); + if (access(dom->cafile, R_OK) == -1) + fatalx("'%s': not readable", dom->cafile, errno_s); + } +} + static void worker_reaper(pid_t pid, int status) { @@ -758,9 +834,11 @@ worker_reaper(pid_t pid, int status) kore_log(LOG_NOTICE, "restarting worker %d", kw->id); kw->restarted = 1; kore_msg_parent_remove(kw); - kore_worker_spawn(idx, kw->id, kw->cpu); - kore_msg_parent_add(kw); + if (!kore_worker_spawn(idx, kw->id, kw->cpu)) + (void)raise(SIGQUIT); + + kore_msg_parent_add(kw); break; } }