From ad95fd1d2b9c6344864857c2ba7634fd87753f8e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 30 Mar 2014 23:08:02 -0400 Subject: [PATCH] journal-remote: add units and read certs from default locations --- Makefile.am | 42 ++++- configure.ac | 9 + src/journal-remote/.gitignore | 1 + src/journal-remote/journal-remote.c | 190 ++++++++++++++++------ src/journal-remote/journal-remote.conf.in | 4 + tmpfiles.d/systemd-remote.conf | 11 ++ units/.gitignore | 8 +- units/systemd-journal-remote.service.in | 24 +++ units/systemd-journal-remote.socket | 15 ++ units/systemd-journal-upload.service.in | 22 +++ 10 files changed, 269 insertions(+), 57 deletions(-) create mode 100644 src/journal-remote/.gitignore create mode 100644 src/journal-remote/journal-remote.conf.in create mode 100644 tmpfiles.d/systemd-remote.conf create mode 100644 units/systemd-journal-remote.service.in create mode 100644 units/systemd-journal-remote.socket create mode 100644 units/systemd-journal-upload.service.in diff --git a/Makefile.am b/Makefile.am index 7fefa5873..9845836a7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -133,8 +133,8 @@ polkitpolicy_in_files = polkitpolicy_files = dist_udevrules_DATA = nodist_udevrules_DATA = -nodist_pkgsysconf_DATA = dist_pkgsysconf_DATA = +nodist_pkgsysconf_DATA = dist_pkgdata_DATA = dist_dbuspolicy_DATA = dist_dbussystemservice_DATA = @@ -164,6 +164,7 @@ AM_CPPFLAGS = \ -DSYSTEM_SYSVRCND_PATH=\"$(SYSTEM_SYSVRCND_PATH)\" \ -DUSER_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/user\" \ -DUSER_DATA_UNIT_PATH=\"$(userunitdir)\" \ + -DCERTIFICATE_ROOT=\"$(CERTIFICATEROOT)\" \ -DCATALOG_DATABASE=\"$(catalogstatedir)/database\" \ -DSYSTEMD_CGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \ -DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" \ @@ -1818,6 +1819,7 @@ nodist_systemunit_DATA += \ dist_tmpfiles_DATA = \ tmpfiles.d/systemd.conf \ tmpfiles.d/systemd-nologin.conf \ + tmpfiles.d/systemd-remote.conf \ tmpfiles.d/tmp.conf \ tmpfiles.d/x11.conf \ tmpfiles.d/var.conf \ @@ -3473,7 +3475,34 @@ systemd_journal_remote_LDADD += \ if HAVE_GNUTLS systemd_journal_remote_LDADD += \ $(GNUTLS_LIBS) + +# systemd-journal-remote make sense mostly with full crypto stack +dist_systemunit_DATA += \ + units/systemd-journal-remote.socket + +nodist_systemunit_DATA += \ + units/systemd-journal-remote.service + +EXTRA_DIST += \ + units/systemd-journal-remote.service.in + +journal-remote-install-hook: journal-install-hook + -$(MKDIR_P) $(DESTDIR)/var/log/journal/remote + -chown 0:0 $(DESTDIR)/var/log/journal/remote + -chmod 755 $(DESTDIR)/var/log/journal/remote + +INSTALL_EXEC_HOOKS += journal-remote-install-hook + endif + +nodist_pkgsysconf_DATA += \ + src/journal-remote/journal-remote.conf + +EXTRA_DIST += \ + src/journal-remote/journal-remote.conf.in + +CLEANFILES += \ + src/journal-remote/journal-remote.conf endif if HAVE_LIBCURL @@ -3495,6 +3524,12 @@ systemd_journal_upload_LDADD = \ libsystemd-journal-internal.la \ libsystemd-shared.la \ $(LIBCURL_LIBS) + +nodist_systemunit_DATA += \ + units/systemd-journal-upload.service + +EXTRA_DIST += \ + units/systemd-journal-upload.service.in endif # using _CFLAGS = in the conditional below would suppress AM_CFLAGS @@ -3663,6 +3698,7 @@ journal-install-hook: -setfacl -nm g:wheel:rx,d:g:wheel:rx $(DESTDIR)/var/log/journal/ journal-uninstall-hook: + -rmdir $(DESTDIR)/var/log/journal/remote -rmdir $(DESTDIR)/var/log/journal/ INSTALL_EXEC_HOOKS += journal-install-hook @@ -5300,6 +5336,7 @@ substitutions = \ '|sysctldir=$(sysctldir)|' \ '|systemgeneratordir=$(systemgeneratordir)|' \ '|usergeneratordir=$(usergeneratordir)|' \ + '|CERTIFICATEROOT=$(CERTIFICATEROOT)|' \ '|PACKAGE_VERSION=$(PACKAGE_VERSION)|' \ '|PACKAGE_NAME=$(PACKAGE_NAME)|' \ '|PACKAGE_URL=$(PACKAGE_URL)|' \ @@ -5352,6 +5389,9 @@ sysctl.d/%: sysctl.d/%.in %.pc: %.pc.in $(SED_PROCESS) +%.conf: %.conf.in + $(SED_PROCESS) + src/core/macros.%: src/core/macros.%.in $(SED_PROCESS) diff --git a/configure.ac b/configure.ac index 6e972e361..94aacc933 100644 --- a/configure.ac +++ b/configure.ac @@ -498,6 +498,14 @@ AC_ARG_WITH([debug-tty], AC_SUBST(DEBUGTTY) +AC_ARG_WITH([certificate-root], + AS_HELP_STRING([--with-certificate-root=PATH], + [Specify the prefix for TLS certificates [/etc/ssl]]), + [CERTIFICATEROOT="$withval"], + [CERTIFICATEROOT="/etc/ssl"]) + +AC_SUBST(CERTIFICATEROOT) + # ------------------------------------------------------------------------------ have_xz=no AC_ARG_ENABLE(xz, AS_HELP_STRING([--disable-xz], [Disable optional XZ support])) @@ -1377,6 +1385,7 @@ AC_MSG_RESULT([ TTY GID: ${TTY_GID} Maximum System UID: ${SYSTEM_UID_MAX} Maximum System GID: ${SYSTEM_GID_MAX} + Certificate root: ${CERTIFICATEROOT} CFLAGS: ${OUR_CFLAGS} ${CFLAGS} CPPFLAGS: ${OUR_CPPFLAGS} ${CPPFLAGS} diff --git a/src/journal-remote/.gitignore b/src/journal-remote/.gitignore new file mode 100644 index 000000000..8112c3c90 --- /dev/null +++ b/src/journal-remote/.gitignore @@ -0,0 +1 @@ +/journal-remote.conf diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 063d6dfa2..5b991e9a3 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -42,6 +42,7 @@ #include "macro.h" #include "strv.h" #include "fileio.h" +#include "conf-parser.h" #include "microhttpd-util.h" #ifdef HAVE_GNUTLS @@ -53,6 +54,10 @@ #define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal" +#define KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" +#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" +#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" + static char* arg_output = NULL; static char* arg_url = NULL; static char* arg_getter = NULL; @@ -65,9 +70,10 @@ static int arg_seal = false; static int http_socket = -1, https_socket = -1; static char** arg_gnutls_log = NULL; -static char *key_pem = NULL; -static char *cert_pem = NULL; -static char *trust_pem = NULL; +static char *arg_key = NULL; +static char *arg_cert = NULL; +static char *arg_trust = NULL; +static bool arg_trust_all = false; /********************************************************************** ********************************************************************** @@ -234,6 +240,7 @@ typedef struct RemoteServer { Writer writer; + bool check_trust; Hashmap *daemons; } RemoteServer; @@ -491,7 +498,7 @@ static int request_handler( "Content-Type: application/vnd.fdo.journal" " is required.\n"); - if (trust_pem) { + if (server->check_trust) { r = check_permissions(connection, &code); if (r < 0) return code; @@ -502,7 +509,11 @@ static int request_handler( return MHD_YES; } -static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) { +static int setup_microhttpd_server(RemoteServer *s, + int fd, + const char *key, + const char *cert, + const char *trust) { struct MHD_OptionItem opts[] = { { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, @@ -530,17 +541,19 @@ static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) { return r; } - if (https) { + if (key) { + assert(cert); + opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem}; + {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem}; + {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; flags |= MHD_USE_SSL; - if (trust_pem) + if (trust) opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem}; + {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; } d = new(MHDDaemonWrapper, 1); @@ -561,7 +574,7 @@ static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) { } log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", - https ? "HTTPS" : "HTTP", fd, d); + key ? "HTTPS" : "HTTP", fd, d); info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); @@ -609,14 +622,16 @@ error: static int setup_microhttpd_socket(RemoteServer *s, const char *address, - bool https) { + const char *key, + const char *cert, + const char *trust) { int fd; fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC); if (fd < 0) return fd; - return setup_microhttpd_server(s, fd, https); + return setup_microhttpd_server(s, fd, key, cert, trust); } static int dispatch_http_event(sd_event_source *event, @@ -690,13 +705,22 @@ static int fd_fd(const char *spec) { } -static int remoteserver_init(RemoteServer *s) { +static int remoteserver_init(RemoteServer *s, + const char* key, + const char* cert, + const char* trust) { int r, n, fd; const char *output_name = NULL; char **file; assert(s); + + if ((arg_listen_raw || arg_listen_http) && trust) { + log_error("Option --trust makes all non-HTTPS connections untrusted."); + return -EINVAL; + } + sd_event_default(&s->events); setup_signals(s); @@ -722,9 +746,9 @@ static int remoteserver_init(RemoteServer *s) { log_info("Received a listening socket (fd:%d)", fd); if (fd == http_socket) - r = setup_microhttpd_server(s, fd, false); + r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); else if (fd == https_socket) - r = setup_microhttpd_server(s, fd, true); + r = setup_microhttpd_server(s, fd, key, cert, trust); else r = add_raw_socket(s, fd); } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { @@ -782,7 +806,7 @@ static int remoteserver_init(RemoteServer *s) { } if (arg_listen_http) { - r = setup_microhttpd_socket(s, arg_listen_http, false); + r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); if (r < 0) return r; @@ -790,7 +814,7 @@ static int remoteserver_init(RemoteServer *s) { } if (arg_listen_https) { - r = setup_microhttpd_socket(s, arg_listen_https, true); + r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); if (r < 0) return r; @@ -959,6 +983,24 @@ static int dispatch_raw_connection_event(sd_event_source *event, ********************************************************************** **********************************************************************/ +static int parse_config(void) { + const ConfigTableItem items[] = { + { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, + { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, + { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, + {}}; + int r; + + r = config_parse(NULL, PKGSYSCONFDIR "/journal-remote.conf", NULL, + "Remote\0", + config_item_table_lookup, items, + false, false, NULL); + if (r < 0) + log_error("Failed to parse configuration file: %s", strerror(-r)); + + return r; +} + static void help(void) { printf("%s [OPTIONS...] {FILE|-}...\n\n" "Write external journal events to a journal file.\n\n" @@ -971,9 +1013,12 @@ static void help(void) { " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" " --[no-]compress Use XZ-compression in the output journal (default: yes)\n" " --[no-]seal Use Event sealing in the output journal (default: no)\n" - " --key=FILENAME Specify key in PEM format\n" - " --cert=FILENAME Specify certificate in PEM format\n" - " --trust=FILENAME Specify CA certificate in PEM format\n" + " --key=FILENAME Specify key in PEM format (default:\n" + " \"" KEY_FILE "\")\n" + " --cert=FILENAME Specify certificate in PEM format (default:\n" + " \"" CERT_FILE "\")\n" + " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" + " \"" TRUST_FILE "\")\n" " --gnutls-log=CATEGORY...\n" " Specify a list of gnutls logging categories\n" " -h --help Show this help and exit\n" @@ -1103,48 +1148,49 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_KEY: - if (key_pem) { + if (arg_key) { log_error("Key file specified twice"); return -EINVAL; } - r = read_full_file(optarg, &key_pem, NULL); - if (r < 0) { - log_error("Failed to read key file: %s", strerror(-r)); - return r; - } - assert(key_pem); + + arg_key = strdup(optarg); + if (!arg_key) + return log_oom(); + break; case ARG_CERT: - if (cert_pem) { + if (arg_cert) { log_error("Certificate file specified twice"); return -EINVAL; } - r = read_full_file(optarg, &cert_pem, NULL); - if (r < 0) { - log_error("Failed to read certificate file: %s", strerror(-r)); - return r; - } - assert(cert_pem); + + arg_cert = strdup(optarg); + if (!arg_cert) + return log_oom(); + break; case ARG_TRUST: -#ifdef HAVE_GNUTLS - if (trust_pem) { - log_error("CA certificate file specified twice"); + if (arg_trust || arg_trust_all) { + log_error("Confusing trusted CA configuration"); return -EINVAL; } - r = read_full_file(optarg, &trust_pem, NULL); - if (r < 0) { - log_error("Failed to read CA certificate file: %s", strerror(-r)); - return r; - } - assert(trust_pem); - break; + + if (streq(optarg, "all")) + arg_trust_all = true; + else { +#ifdef HAVE_GNUTLS + arg_trust = strdup(optarg); + if (!arg_trust) + return log_oom(); #else - log_error("Option --trust is not available."); - return -EINVAL; + log_error("Option --trust is not available."); + return -EINVAL; #endif + } + + break; case 'o': if (arg_output) { @@ -1198,17 +1244,43 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } - if (arg_listen_https && !(key_pem && cert_pem)) { - log_error("Options --key and --cert must be used when using HTTPS."); - return -EINVAL; - } - if (optind < argc) arg_files = argv + optind; return 1 /* work to do */; } +static int load_certificates(char **key, char **cert, char **trust) { + int r; + + r = read_full_file(arg_key ?: KEY_FILE, key, NULL); + if (r < 0) { + log_error("Failed to read key from file '%s': %s", + arg_key ?: KEY_FILE, strerror(-r)); + return r; + } + + r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); + if (r < 0) { + log_error("Failed to read certificate from file '%s': %s", + arg_cert ?: CERT_FILE, strerror(-r)); + return r; + } + + if (arg_trust_all) + log_info("Certificate checking disabled."); + else { + r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); + if (r < 0) { + log_error("Failed to read CA certificate file '%s': %s", + arg_trust ?: TRUST_FILE, strerror(-r)); + return r; + } + } + + return 0; +} + static int setup_gnutls_logger(char **categories) { if (!arg_listen_http && !arg_listen_https) return 0; @@ -1237,10 +1309,15 @@ static int setup_gnutls_logger(char **categories) { int main(int argc, char **argv) { RemoteServer s = {}; int r, r2; + _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; log_show_color(true); log_parse_environment(); + r = parse_config(); + if (r < 0) + return EXIT_FAILURE; + r = parse_argv(argc, argv); if (r <= 0) return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; @@ -1249,7 +1326,10 @@ int main(int argc, char **argv) { if (r < 0) return EXIT_FAILURE; - if (remoteserver_init(&s) < 0) + if (load_certificates(&key, &cert, &trust) < 0) + return EXIT_FAILURE; + + if (remoteserver_init(&s, key, cert, trust) < 0) return EXIT_FAILURE; sd_event_set_watchdog(s.events, true); @@ -1279,5 +1359,9 @@ int main(int argc, char **argv) { sd_notify(false, "STATUS=Shutting down..."); + free(arg_key); + free(arg_cert); + free(arg_trust); + return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in new file mode 100644 index 000000000..a06c7e036 --- /dev/null +++ b/src/journal-remote/journal-remote.conf.in @@ -0,0 +1,4 @@ +[Remote] +# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem +# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem +# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/tmpfiles.d/systemd-remote.conf b/tmpfiles.d/systemd-remote.conf new file mode 100644 index 000000000..1b8973a88 --- /dev/null +++ b/tmpfiles.d/systemd-remote.conf @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +# See tmpfiles.d(5) for details + +z /var/log/journal/remote 2755 root systemd-journal-remote - - +z /run/log/journal/remote 2755 root systemd-journal-remote - - diff --git a/units/.gitignore b/units/.gitignore index 5d68927e2..d9b60ac0f 100644 --- a/units/.gitignore +++ b/units/.gitignore @@ -31,17 +31,19 @@ /systemd-hostnamed.service /systemd-hybrid-sleep.service /systemd-initctl.service +/systemd-journal-catalog-update.service /systemd-journal-flush.service /systemd-journal-gatewayd.service -/systemd-journal-catalog-update.service +/systemd-journal-remote.service +/systemd-journal-upload.service /systemd-journald.service /systemd-kexec.service /systemd-localed.service /systemd-logind.service /systemd-machined.service /systemd-modules-load.service -/systemd-networkd.service /systemd-networkd-wait-online.service +/systemd-networkd.service /systemd-nspawn@.service /systemd-poweroff.service /systemd-quotacheck.service @@ -67,9 +69,9 @@ /systemd-udev-settle.service /systemd-udev-trigger.service /systemd-udevd.service +/systemd-update-done.service /systemd-update-utmp-runlevel.service /systemd-update-utmp.service -/systemd-update-done.service /systemd-user-sessions.service /systemd-vconsole-setup.service /user@.service diff --git a/units/systemd-journal-remote.service.in b/units/systemd-journal-remote.service.in new file mode 100644 index 000000000..4a898d62f --- /dev/null +++ b/units/systemd-journal-remote.service.in @@ -0,0 +1,24 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Remote Sink Service +Requires=systemd-journal-remote.socket + +[Service] +ExecStart=@rootlibexecdir@/systemd-journal-remote \ + --listen-https=-3 \ + --output=/var/log/journal/remote/ +User=systemd-journal-remote +Group=systemd-journal-remote +PrivateTmp=yes +PrivateDevices=yes +PrivateNetwork=yes +WatchdogSec=10min + +[Install] +Also=systemd-journal-remote.socket diff --git a/units/systemd-journal-remote.socket b/units/systemd-journal-remote.socket new file mode 100644 index 000000000..076dcae8a --- /dev/null +++ b/units/systemd-journal-remote.socket @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Remote Sink Socket + +[Socket] +ListenStream=19532 + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-journal-upload.service.in b/units/systemd-journal-upload.service.in new file mode 100644 index 000000000..638829164 --- /dev/null +++ b/units/systemd-journal-upload.service.in @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Remote Upload Service + +[Service] +ExecStart=@rootlibexecdir@/systemd-journal-upload \ + --save-state +User=systemd-journal-upload +Group=systemd-journal-uplaod +PrivateTmp=yes +PrivateDevices=yes +WatchdogSec=20min + +[Install] +WantedBy=multi-user.target +After=network.target -- 2.30.2