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:
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,
¤t_domain->certkey, ¤t_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;
}
}