chiark / gitweb /
journal-remote: add units and read certs from default locations
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 31 Mar 2014 03:08:02 +0000 (23:08 -0400)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 16 Jul 2014 02:23:49 +0000 (22:23 -0400)
Makefile.am
configure.ac
src/journal-remote/.gitignore [new file with mode: 0644]
src/journal-remote/journal-remote.c
src/journal-remote/journal-remote.conf.in [new file with mode: 0644]
tmpfiles.d/systemd-remote.conf [new file with mode: 0644]
units/.gitignore
units/systemd-journal-remote.service.in [new file with mode: 0644]
units/systemd-journal-remote.socket [new file with mode: 0644]
units/systemd-journal-upload.service.in [new file with mode: 0644]

index 7fefa58..9845836 100644 (file)
@@ -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)
 
index 6e972e3..94aacc9 100644 (file)
@@ -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 (file)
index 0000000..8112c3c
--- /dev/null
@@ -0,0 +1 @@
+/journal-remote.conf
index 063d6df..5b991e9 100644 (file)
@@ -42,6 +42,7 @@
 #include "macro.h"
 #include "strv.h"
 #include "fileio.h"
+#include "conf-parser.h"
 #include "microhttpd-util.h"
 
 #ifdef HAVE_GNUTLS
 
 #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 (file)
index 0000000..a06c7e0
--- /dev/null
@@ -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 (file)
index 0000000..1b8973a
--- /dev/null
@@ -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 - -
index 5d68927..d9b60ac 100644 (file)
 /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 (file)
index 0000000..4a898d6
--- /dev/null
@@ -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 (file)
index 0000000..076dcae
--- /dev/null
@@ -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 (file)
index 0000000..6388291
--- /dev/null
@@ -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