]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add credentials logic
authorLennart Poettering <lennart@poettering.net>
Thu, 23 Jul 2020 06:49:52 +0000 (08:49 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 25 Aug 2020 17:45:35 +0000 (19:45 +0200)
Fixes: #15778 #16060
20 files changed:
src/basic/path-util.c
src/basic/path-util.h
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/manager.c
src/core/manager.h
src/core/meson.build
src/core/mount.c
src/core/service.c
src/core/socket.c
src/core/swap.c
src/core/unit.c
src/core/unit.h
src/shared/bus-unit-util.c
src/shared/exit-status.c
src/shared/exit-status.h

index c463ae23ab9c6779b92237f8b1dbd016b426bf2f..deffa8a425559488cbdeeb160c18a3f767fda69a 100644 (file)
@@ -14,6 +14,7 @@
 
 #include "alloc-util.h"
 #include "extract-word.h"
+#include "fd-util.h"
 #include "fs-util.h"
 #include "glob-util.h"
 #include "log.h"
@@ -1127,3 +1128,9 @@ bool prefixed_path_strv_contains(char **l, const char *path) {
 
         return false;
 }
+
+bool credential_name_valid(const char *s) {
+        /* We want that credential names are both valid in filenames (since that's our primary way to pass
+         * them around) and as fdnames (which is how we might want to pass them around eventually) */
+        return filename_is_valid(s) && fdname_is_valid(s);
+}
index 99c4e357826bbb0f27d42d84ad0e05346bca3336..1afbebd77feda747114e7416d2d83bbcdcd12028 100644 (file)
@@ -173,3 +173,5 @@ static inline const char *empty_to_root(const char *path) {
 
 bool path_strv_contains(char **l, const char *path);
 bool prefixed_path_strv_contains(char **l, const char *path);
+
+bool credential_name_valid(const char *s);
index c96c654ff03e79deaf91e110918b2a4f36ad2781..9042f0baa8650e852f3da542fd5ce8e276791cce 100644 (file)
@@ -748,6 +748,82 @@ static int property_get_log_extra_fields(
         return sd_bus_message_close_container(reply);
 }
 
+static int property_get_set_credential(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        ExecContext *c = userdata;
+        ExecSetCredential *sc;
+        Iterator iterator;
+        int r;
+
+        assert(bus);
+        assert(c);
+        assert(property);
+        assert(reply);
+
+        r = sd_bus_message_open_container(reply, 'a', "(say)");
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(sc, c->set_credentials, iterator) {
+
+                r = sd_bus_message_open_container(reply, 'r', "say");
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_append(reply, "s", sc->id);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_append_array(reply, 'y', sc->data, sc->size);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_close_container(reply);
+                if (r < 0)
+                        return r;
+        }
+
+        return sd_bus_message_close_container(reply);
+}
+
+static int property_get_load_credential(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        ExecContext *c = userdata;
+        char **i, **j;
+        int r;
+
+        assert(bus);
+        assert(c);
+        assert(property);
+        assert(reply);
+
+        r = sd_bus_message_open_container(reply, 'a', "(ss)");
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH_PAIR(i, j, c->load_credentials) {
+                r = sd_bus_message_append(reply, "(ss)", *i, *j);
+                if (r < 0)
+                        return r;
+        }
+
+        return sd_bus_message_close_container(reply);
+}
+
 static int property_get_root_hash(
                 sd_bus *bus,
                 const char *path,
@@ -965,6 +1041,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("SetCredential", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1794,6 +1872,146 @@ int bus_exec_context_set_transient_property(
 
                 return 1;
 
+        } else if (streq(name, "SetCredential")) {
+                bool isempty = true;
+
+                r = sd_bus_message_enter_container(message, 'a', "(say)");
+                if (r < 0)
+                        return r;
+
+                for (;;) {
+                        const char *id;
+                        const void *p;
+                        size_t sz;
+
+                        r = sd_bus_message_enter_container(message, 'r', "say");
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+
+                        r = sd_bus_message_read(message, "s", &id);
+                        if (r < 0)
+                                return r;
+
+                        r = sd_bus_message_read_array(message, 'y', &p, &sz);
+                        if (r < 0)
+                                return r;
+
+                        r = sd_bus_message_exit_container(message);
+                        if (r < 0)
+                                return r;
+
+                        if (!credential_name_valid(id))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential ID is invalid: %s", id);
+
+                        isempty = false;
+
+                        if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                                _cleanup_free_ char *a = NULL, *b = NULL;
+                                _cleanup_free_ void *copy = NULL;
+                                ExecSetCredential *old;
+
+                                copy = memdup(p, sz);
+                                if (!copy)
+                                        return -ENOMEM;
+
+                                old = hashmap_get(c->set_credentials, id);
+                                if (old) {
+                                        free_and_replace(old->data, copy);
+                                        old->size = sz;
+                                } else {
+                                        _cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
+
+                                        sc = new0(ExecSetCredential, 1);
+                                        if (!sc)
+                                                return -ENOMEM;
+
+                                        sc->id = strdup(id);
+                                        if (!sc->id)
+                                                return -ENOMEM;
+
+                                        sc->data = TAKE_PTR(copy);
+                                        sc->size = sz;
+
+                                        r = hashmap_ensure_allocated(&c->set_credentials, &exec_set_credential_hash_ops);
+                                        if (r < 0)
+                                                return r;
+
+                                        r = hashmap_put(c->set_credentials, sc->id, sc);
+                                        if (r < 0)
+                                                return r;
+
+                                        TAKE_PTR(sc);
+                                }
+
+                                a = specifier_escape(id);
+                                if (!a)
+                                        return -ENOMEM;
+
+                                b = cescape_length(p, sz);
+                                if (!b)
+                                        return -ENOMEM;
+
+                                (void) unit_write_settingf(u, flags, name, "%s=%s:%s", name, a, b);
+                        }
+                }
+
+                r = sd_bus_message_exit_container(message);
+                if (r < 0)
+                        return r;
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
+                        c->set_credentials = hashmap_free(c->set_credentials);
+                        (void) unit_write_settingf(u, flags, name, "%s=", name);
+                }
+
+                return 1;
+
+        } else if (streq(name, "LoadCredential")) {
+                bool isempty = true;
+
+                r = sd_bus_message_enter_container(message, 'a', "(ss)");
+                if (r < 0)
+                        return r;
+
+                for (;;) {
+                        const char *id, *source;
+
+                        r = sd_bus_message_read(message, "(ss)", &id, &source);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+
+                        if (!credential_name_valid(id))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential ID is invalid: %s", id);
+
+                        if (!(path_is_absolute(source) ? path_is_normalized(source) : credential_name_valid(source)))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential source is invalid: %s", source);
+
+                        isempty = false;
+
+                        if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                                r = strv_extend_strv(&c->load_credentials, STRV_MAKE(id, source), /* filter_duplicates = */ false);
+                                if (r < 0)
+                                        return r;
+
+                                (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source);
+                        }
+                }
+
+                r = sd_bus_message_exit_container(message);
+                if (r < 0)
+                        return r;
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
+                        c->load_credentials = strv_free(c->load_credentials);
+                        (void) unit_write_settingf(u, flags, name, "%s=", name);
+                }
+
+                return 1;
+
         } else if (streq(name, "SyslogLevel")) {
                 int32_t level;
 
index d5107288a179a74d68fa35b8f9eb0f897f66566c..81829007b4a45ee8b0786ec2e03a5a697312c726 100644 (file)
@@ -6,6 +6,7 @@
 #include <sys/eventfd.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
+#include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/shm.h>
@@ -32,6 +33,7 @@
 
 #include "sd-messages.h"
 
+#include "acl-util.h"
 #include "af-list.h"
 #include "alloc-util.h"
 #if HAVE_APPARMOR
@@ -41,8 +43,8 @@
 #include "barrier.h"
 #include "cap-list.h"
 #include "capability-util.h"
-#include "chown-recursive.h"
 #include "cgroup-setup.h"
+#include "chown-recursive.h"
 #include "cpu-set-util.h"
 #include "def.h"
 #include "env-file.h"
@@ -51,6 +53,7 @@
 #include "execute.h"
 #include "exit-status.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "format-util.h"
 #include "fs-util.h"
 #include "glob-util.h"
@@ -64,6 +67,7 @@
 #include "memory-util.h"
 #include "missing_fs.h"
 #include "mkdir.h"
+#include "mountpoint-util.h"
 #include "namespace.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -85,6 +89,7 @@
 #include "strv.h"
 #include "syslog-util.h"
 #include "terminal-util.h"
+#include "tmpfile-util.h"
 #include "umask-util.h"
 #include "unit.h"
 #include "user-util.h"
@@ -1417,6 +1422,14 @@ static bool context_has_no_new_privileges(const ExecContext *c) {
                 c->protect_hostname;
 }
 
+static bool exec_context_has_credentials(const ExecContext *context) {
+
+        assert(context);
+
+        return !hashmap_isempty(context->set_credentials) ||
+                context->load_credentials;
+}
+
 #if HAVE_SECCOMP
 
 static bool skip_seccomp_unavailable(const Unit* u, const char* msg) {
@@ -1725,7 +1738,7 @@ static int build_environment(
         assert(p);
         assert(ret);
 
-#define N_ENV_VARS 15
+#define N_ENV_VARS 16
         our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
         if (!our_env)
                 return -ENOMEM;
@@ -1873,6 +1886,14 @@ static int build_environment(
                 our_env[n_env++] = x;
         }
 
+        if (exec_context_has_credentials(c) && p->prefix[EXEC_DIRECTORY_RUNTIME]) {
+                x = strjoin("CREDENTIALS_DIRECTORY=", p->prefix[EXEC_DIRECTORY_RUNTIME], "/credentials/", u->id);
+                if (!x)
+                        return -ENOMEM;
+
+                our_env[n_env++] = x;
+        }
+
         our_env[n_env++] = NULL;
         assert(n_env <= N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
 #undef N_ENV_VARS
@@ -2378,6 +2399,437 @@ fail:
         return r;
 }
 
+static int write_credential(
+                int dfd,
+                const char *id,
+                const void *data,
+                size_t size,
+                uid_t uid,
+                bool ownership_ok) {
+
+        _cleanup_(unlink_and_freep) char *tmp = NULL;
+        _cleanup_close_ int fd = -1;
+        int r;
+
+        r = tempfn_random_child("", "cred", &tmp);
+        if (r < 0)
+                return r;
+
+        fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
+        if (fd < 0) {
+                tmp = mfree(tmp);
+                return -errno;
+        }
+
+        r = loop_write(fd, data, size, /* do_pool = */ false);
+        if (r < 0)
+                return r;
+
+        if (fchmod(fd, 0400) < 0) /* Take away "w" bit */
+                return -errno;
+
+        if (uid_is_valid(uid) && uid != getuid()) {
+#if HAVE_ACL
+                r = fd_add_uid_acl_permission(fd, uid, /* read = */ true, /* write = */ false, /* execute = */ false);
+#else
+                r = -EOPNOTSUPP;
+#endif
+                if (r < 0) {
+                        if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
+                                return r;
+
+                        if (!ownership_ok) /* Ideally we use ACLs, since we can neatly express what we want
+                                            * to express: that the user gets read access and nothing
+                                            * else. But if the backing fs can't support that (e.g. ramfs)
+                                            * then we can use file ownership instead. But that's only safe if
+                                            * we can then re-mount the whole thing read-only, so that the
+                                            * user can no longer chmod() the file to gain write access. */
+                                return r;
+
+                        if (fchown(fd, uid, (gid_t) -1) < 0)
+                                return -errno;
+                }
+        }
+
+        if (renameat(dfd, tmp, dfd, id) < 0)
+                return -errno;
+
+        tmp = mfree(tmp);
+        return 0;
+}
+
+#define CREDENTIALS_BYTES_MAX (1024LU * 1024LU) /* Refuse to pass more than 1M, after all this is unswappable memory */
+
+static int acquire_credentials(
+                const ExecContext *context,
+                const ExecParameters *params,
+                const char *p,
+                uid_t uid,
+                bool ownership_ok) {
+
+        uint64_t left = CREDENTIALS_BYTES_MAX;
+        _cleanup_close_ int dfd = -1;
+        ExecSetCredential *sc;
+        char **id, **fn;
+        Iterator iterator;
+        int r;
+
+        assert(context);
+        assert(p);
+
+        dfd = open(p, O_DIRECTORY|O_CLOEXEC);
+        if (dfd < 0)
+                return -errno;
+
+        /* First we use the literally specified credentials. Note that they might be overriden again below,
+         * and thus act as a "default" if the same credential is specified multiple times */
+        HASHMAP_FOREACH(sc, context->set_credentials, iterator) {
+                size_t add;
+
+                add = strlen(sc->id) + sc->size;
+                if (add > left)
+                        return -E2BIG;
+
+                r = write_credential(dfd, sc->id, sc->data, sc->size, uid, ownership_ok);
+                if (r < 0)
+                        return r;
+
+                left -= add;
+        }
+
+        /* Then, load credential off disk (or acquire via AF_UNIX socket) */
+        STRV_FOREACH_PAIR(id, fn, context->load_credentials) {
+                ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
+                _cleanup_(erase_and_freep) char *data = NULL;
+                _cleanup_free_ char *j = NULL;
+                const char *source;
+                size_t size, add;
+
+                if (path_is_absolute(*fn)) {
+                        /* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
+                        source = *fn;
+                        flags |= READ_FULL_FILE_CONNECT_SOCKET;
+                } else if (params->received_credentials) {
+                        /* If this is a relative path, take it relative to the credentials we received
+                         * ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
+                         * on a credential store, i.e. this is guaranteed to be regular files. */
+                        j = path_join(params->received_credentials, *fn);
+                        if (!j)
+                                return -ENOMEM;
+
+                        source = j;
+                } else
+                        source = NULL;
+
+                if (source)
+                        r = read_full_file_full(AT_FDCWD, source, flags, &data, &size);
+                else
+                        r = -ENOENT;
+                if (r == -ENOENT &&
+                    faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) /* If the source file doesn't exist, but we already acquired the key otherwise, then don't fail */
+                        continue;
+                if (r < 0)
+                        return r;
+
+                add = strlen(*id) + size;
+                if (add > left)
+                        return -E2BIG;
+
+                r = write_credential(dfd, *id, data, size, uid, ownership_ok);
+                if (r < 0)
+                        return r;
+
+                left -= add;
+        }
+
+        if (fchmod(dfd, 0500) < 0) /* Now take away the "w" bit */
+                return -errno;
+
+        /* After we created all keys with the right perms, also make sure the credential store as a whole is
+         * accessible */
+
+        if (uid_is_valid(uid) && uid != getuid()) {
+#if HAVE_ACL
+                r = fd_add_uid_acl_permission(dfd, uid, /* read = */ true, /* write = */ false, /* execute = */ true);
+#else
+                r = -EOPNOTSUPP;
+#endif
+                if (r < 0) {
+                        if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
+                                return r;
+
+                        if (!ownership_ok)
+                                return r;
+
+                        if (fchown(dfd, uid, (gid_t) -1) < 0)
+                                return -errno;
+                }
+        }
+
+        return 0;
+}
+
+static int setup_credentials_internal(
+                const ExecContext *context,
+                const ExecParameters *params,
+                const char *final,        /* This is where the credential store shall eventually end up at */
+                const char *workspace,    /* This is where we can prepare it before moving it to the final place */
+                bool reuse_workspace,     /* Whether to reuse any existing workspace mount if it already is a mount */
+                bool must_mount,          /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
+                uid_t uid) {
+
+        int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
+                                   * if we mounted something; false if we definitely can't mount anything */
+        bool final_mounted;
+        const char *where;
+
+        assert(context);
+        assert(final);
+        assert(workspace);
+
+        if (reuse_workspace) {
+                r = path_is_mount_point(workspace, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse it, let's keep this in mind */
+                else
+                        workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
+        } else
+                workspace_mounted = -1; /* ditto */
+
+        r = path_is_mount_point(final, NULL, 0);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                /* If the final place already has something mounted, we use that. If the workspace also has
+                 * something mounted we assume it's actually the same mount (but with MS_RDONLY
+                 * different). */
+                final_mounted = true;
+
+                if (workspace_mounted < 0) {
+                        /* If the final place is mounted, but the workspace we isn't, then let's bind mount
+                         * the final version to the workspace, and make it writable, so that we can make
+                         * changes */
+
+                        if (mount(final, workspace, NULL, MS_BIND|MS_REC, NULL) < 0)
+                                return -errno;
+
+                        if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
+                                return -errno;
+
+                        workspace_mounted = true;
+                }
+        } else
+                final_mounted = false;
+
+        if (workspace_mounted < 0) {
+                /* Nothing is mounted on the workspace yet, let's try to mount something now */
+                for (int try = 0;; try++) {
+
+                        if (try == 0) {
+                                /* Try "ramfs" first, since it's not swap backed */
+                                if (mount("ramfs", workspace, "ramfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, "mode=0700") >= 0) {
+                                        workspace_mounted = true;
+                                        break;
+                                }
+
+                        } else if (try == 1) {
+                                _cleanup_free_ char *opts = NULL;
+
+                                if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%lu", CREDENTIALS_BYTES_MAX) < 0)
+                                        return -ENOMEM;
+
+                                /* Fall back to "tmpfs" otherwise */
+                                if (mount("tmpfs", workspace, "tmpfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, opts) >= 0) {
+                                        workspace_mounted = true;
+                                        break;
+                                }
+
+                        } else {
+                                /* If that didn't work, try to make a bind mount from the final to the workspace, so that we can make it writable there. */
+                                if (mount(final, workspace, NULL, MS_BIND|MS_REC, NULL) < 0) {
+                                        if (!ERRNO_IS_PRIVILEGE(errno)) /* Propagate anything that isn't a permission problem */
+                                                return -errno;
+
+                                        if (must_mount) /* If we it's not OK to use the plain directory
+                                                         * fallback, propagate all errors too */
+                                                return -errno;
+
+                                        /* If we lack privileges to bind mount stuff, then let's gracefully
+                                         * proceed for compat with container envs, and just use the final dir
+                                         * as is. */
+
+                                        workspace_mounted = false;
+                                        break;
+                                }
+
+                                /* Make the new bind mount writable (i.e. drop MS_RDONLY) */
+                                if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
+                                        return -errno;
+
+                                workspace_mounted = true;
+                                break;
+                        }
+                }
+        }
+
+        assert(!must_mount || workspace_mounted > 0);
+        where = workspace_mounted ? workspace : final;
+
+        r = acquire_credentials(context, params, where, uid, workspace_mounted);
+        if (r < 0)
+                return r;
+
+        if (workspace_mounted) {
+                /* Make workspace read-only now, so that any bind mount we make from it defaults to read-only too */
+                if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
+                        return -errno;
+
+                /* And mount it to the final place, read-only */
+                if (final_mounted) {
+                        if (umount2(workspace, MNT_DETACH|UMOUNT_NOFOLLOW) < 0)
+                                return -errno;
+                } else {
+                        if (mount(workspace, final, NULL, MS_MOVE, NULL) < 0)
+                                return -errno;
+                }
+        } else {
+                _cleanup_free_ char *parent = NULL;
+
+                /* If we do not have our own mount put used the plain directory fallback, then we need to
+                 * open access to the top-level credential directory and the per-service directory now */
+
+                parent = dirname_malloc(final);
+                if (!parent)
+                        return -ENOMEM;
+                if (chmod(parent, 0755) < 0)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+static int setup_credentials(
+                const ExecContext *context,
+                const ExecParameters *params,
+                const char *unit,
+                uid_t uid) {
+
+        _cleanup_free_ char *p = NULL, *q = NULL;
+        const char *i;
+        int r;
+
+        assert(context);
+        assert(params);
+
+        if (!exec_context_has_credentials(context))
+                return 0;
+
+        if (!params->prefix[EXEC_DIRECTORY_RUNTIME])
+                return -EINVAL;
+
+        /* This where we'll place stuff when we are done; this main credentials directory is world-readable,
+         * and the subdir we mount over with a read-only file system readable by the service's user */
+        q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials");
+        if (!q)
+                return -ENOMEM;
+
+        r = mkdir_label(q, 0755); /* top-level dir: world readable/searchable */
+        if (r < 0 && r != -EEXIST)
+                return r;
+
+        p = path_join(q, unit);
+        if (!p)
+                return -ENOMEM;
+
+        r = mkdir_label(p, 0700); /* per-unit dir: private to user */
+        if (r < 0 && r != -EEXIST)
+                return r;
+
+        r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
+        if (r < 0) {
+                _cleanup_free_ char *t = NULL, *u = NULL;
+
+                /* If this is not a privilege or support issue then propagate the error */
+                if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
+                        return r;
+
+                /* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
+                 * it into place, so that users can't access half-initialized credential stores. */
+                t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
+                if (!t)
+                        return -ENOMEM;
+
+                /* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
+                 * directory outside of /run/credentials/ first, and then move it over to /run/credentials/
+                 * after it is fully set up */
+                u = path_join(t, unit);
+                if (!u)
+                        return -ENOMEM;
+
+                FOREACH_STRING(i, t, u) {
+                        r = mkdir_label(i, 0700);
+                        if (r < 0 && r != -EEXIST)
+                                return r;
+                }
+
+                r = setup_credentials_internal(
+                                context,
+                                params,
+                                p,       /* final mount point */
+                                u,       /* temporary workspace to overmount */
+                                true,    /* reuse the workspace if it is already a mount */
+                                false,   /* it's OK to fall back to a plain directory if we can't mount anything */
+                                uid);
+
+                (void) rmdir(u); /* remove the workspace again if we can. */
+
+                if (r < 0)
+                        return r;
+
+        } else if (r == 0) {
+
+                /* We managed to set up a mount namespace, and are now in a child. That's great. In this case
+                 * we can use the same directory for all cases, after turning off propagation. Question
+                 * though is: where do we turn off propagation exactly, and where do we place the workspace
+                 * directory? We need some place that is guaranteed to be a mount point in the host, and
+                 * which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
+                 * since we ultimately want to move the resulting file system there, i.e. we need propagation
+                 * for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
+                 * would be visible in the host mount table all the time, which we want to avoid. Hence, what
+                 * we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
+                 * /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
+                 * propagation on the former, and then overmount the latter.
+                 *
+                 * Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
+                 * for this purpose, but there are few other candidates that work equally well for us, and
+                 * given that the we do this in a privately namespaced short-lived single-threaded process
+                 * that noone else sees this should be OK to do.*/
+
+                if (mount(NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL) < 0) /* Turn off propagation from our namespace to host */
+                        goto child_fail;
+
+                r = setup_credentials_internal(
+                                context,
+                                params,
+                                p,           /* final mount point */
+                                "/dev/shm",  /* temporary workspace to overmount */
+                                false,       /* do not reuse /dev/shm if it is already a mount, under no circumstances */
+                                true,        /* insist that something is mounted, do not allow fallback to plain directory */
+                                uid);
+                if (r < 0)
+                        goto child_fail;
+
+                _exit(EXIT_SUCCESS);
+
+        child_fail:
+                _exit(EXIT_FAILURE);
+        }
+
+        return 0;
+}
+
 #if ENABLE_SMACK
 static int setup_smack(
                 const ExecContext *context,
@@ -3489,6 +3941,14 @@ static int exec_child(
                         return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
         }
 
+        if (FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
+                r = setup_credentials(context, params, unit->id, uid);
+                if (r < 0) {
+                        *exit_status = EXIT_CREDENTIALS;
+                        return log_unit_error_errno(unit, r, "Failed to set up credentials: %m");
+                }
+        }
+
         r = build_environment(
                         unit,
                         context,
@@ -4276,6 +4736,9 @@ void exec_context_done(ExecContext *c) {
         c->network_namespace_path = mfree(c->network_namespace_path);
 
         c->log_namespace = mfree(c->log_namespace);
+
+        c->load_credentials = strv_free(c->load_credentials);
+        c->set_credentials = hashmap_free(c->set_credentials);
 }
 
 int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
@@ -4304,6 +4767,26 @@ int exec_context_destroy_runtime_directory(const ExecContext *c, const char *run
         return 0;
 }
 
+int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_prefix, const char *unit) {
+        _cleanup_free_ char *p = NULL;
+
+        assert(c);
+
+        if (!runtime_prefix || !unit)
+                return 0;
+
+        p = path_join(runtime_prefix, "credentials", unit);
+        if (!p)
+                return -ENOMEM;
+
+        /* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
+         * unmount it, and afterwards remove the mount point */
+        (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
+        (void) rm_rf(p, REMOVE_ROOT|REMOVE_CHMOD);
+
+        return 0;
+}
+
 static void exec_command_done(ExecCommand *c) {
         assert(c);
 
@@ -5812,6 +6295,17 @@ void exec_params_clear(ExecParameters *p) {
         p->exec_fd = safe_close(p->exec_fd);
 }
 
+ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc) {
+        if (!sc)
+                return NULL;
+
+        free(sc->id);
+        free(sc->data);
+        return mfree(sc);
+}
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
+
 static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
         [EXEC_INPUT_NULL] = "null",
         [EXEC_INPUT_TTY] = "tty",
index 1ea7e51fd799fa70237c69b51e81afacc0812dbf..810e585fa8147ce30ac0d0df592db5f1cbb7f7da 100644 (file)
@@ -145,6 +145,13 @@ typedef enum ExecCleanMask {
         _EXEC_CLEAN_MASK_INVALID = -1,
 } ExecCleanMask;
 
+/* A credential configured with SetCredential= */
+typedef struct ExecSetCredential {
+        char *id;
+        void *data;
+        size_t size;
+} ExecSetCredential;
+
 /* Encodes configuration parameters applied to invoked commands. Does not carry runtime data, but only configuration
  * changes sourced from unit files and suchlike. ExecContext objects are usually embedded into Unit objects, and do not
  * change after being loaded. */
@@ -303,6 +310,9 @@ struct ExecContext {
         ExecDirectory directories[_EXEC_DIRECTORY_TYPE_MAX];
         ExecPreserveMode runtime_directory_preserve_mode;
         usec_t timeout_clean_usec;
+
+        Hashmap *set_credentials; /* output id â†’ ExecSetCredential */
+        char **load_credentials; /* pairs of output id, path/input id */
 };
 
 static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
@@ -321,11 +331,12 @@ typedef enum ExecFlags {
         EXEC_CGROUP_DELEGATE   = 1 << 6,
         EXEC_IS_CONTROL        = 1 << 7,
         EXEC_CONTROL_CGROUP    = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */
+        EXEC_WRITE_CREDENTIALS = 1 << 9, /* Set up the credential store logic */
 
         /* The following are not used by execute.c, but by consumers internally */
-        EXEC_PASS_FDS          = 1 << 9,
-        EXEC_SETENV_RESULT     = 1 << 10,
-        EXEC_SET_WATCHDOG      = 1 << 11,
+        EXEC_PASS_FDS          = 1 << 10,
+        EXEC_SETENV_RESULT     = 1 << 11,
+        EXEC_SET_WATCHDOG      = 1 << 12,
 } ExecFlags;
 
 /* Parameters for a specific invocation of a command. This structure is put together right before a command is
@@ -345,6 +356,7 @@ struct ExecParameters {
         const char *cgroup_path;
 
         char **prefix;
+        const char *received_credentials;
 
         const char *confirm_spawn;
 
@@ -386,6 +398,7 @@ void exec_context_done(ExecContext *c);
 void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix);
 
 int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_root);
+int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_root, const char *unit);
 
 const char* exec_context_fdname(const ExecContext *c, int fd_index);
 
@@ -418,6 +431,11 @@ void exec_params_clear(ExecParameters *p);
 
 bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
 
+ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free);
+
+extern const struct hash_ops exec_set_credential_hash_ops;
+
 const char* exec_output_to_string(ExecOutput i) _const_;
 ExecOutput exec_output_from_string(const char *s) _pure_;
 
index 9a1fcca46e61eac12f77d58bb1084d7f27c867cf..45147f0d57304ae77c2a1fb37c0e1a12126e08b5 100644 (file)
@@ -147,6 +147,8 @@ $1.LogsDirectoryMode,            config_parse_mode,                  0,
 $1.LogsDirectory,                config_parse_exec_directories,      0,                             offsetof($1, exec_context.directories[EXEC_DIRECTORY_LOGS].paths)
 $1.ConfigurationDirectoryMode,   config_parse_mode,                  0,                             offsetof($1, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
 $1.ConfigurationDirectory,       config_parse_exec_directories,      0,                             offsetof($1, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
+$1.SetCredential,                config_parse_set_credential,        0,                             offsetof($1, exec_context)
+$1.LoadCredential,               config_parse_load_credential,       0,                             offsetof($1, exec_context)
 $1.TimeoutCleanSec,              config_parse_sec,                   0,                             offsetof($1, exec_context.timeout_clean_usec)
 $1.ProtectHostname,              config_parse_bool,                  0,                             offsetof($1, exec_context.protect_hostname)
 m4_ifdef(`HAVE_PAM',
index df93fbb28fd731b2adfd645d8d15d40685fff47d..a8eee285021cd35ec0aebccd5cfa6aeb39f79065 100644 (file)
@@ -60,6 +60,7 @@
 #include "unit-name.h"
 #include "unit-printf.h"
 #include "user-util.h"
+#include "utf8.h"
 #include "web-util.h"
 
 static int parse_socket_protocol(const char *s) {
@@ -4268,6 +4269,155 @@ int config_parse_exec_directories(
         }
 }
 
+int config_parse_set_credential(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_free_ char *word = NULL, *k = NULL, *unescaped = NULL;
+        ExecContext *context = data;
+        ExecSetCredential *old;
+        Unit *u = userdata;
+        const char *p;
+        int r, l;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(context);
+
+        if (isempty(rvalue)) {
+                /* Empty assignment resets the list */
+                context->set_credentials = hashmap_free(context->set_credentials);
+                return 0;
+        }
+
+        p = rvalue;
+        r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r <= 0 || !p) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        r = unit_full_printf(u, word, &k);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
+                return 0;
+        }
+        if (!credential_name_valid(k)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", k);
+                return 0;
+        }
+
+        /* We support escape codes here, so that users can insert trailing \n if they like */
+        l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
+        if (l < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, l, "Can't unescape \"%s\", ignoring: %m", p);
+                return 0;
+        }
+
+        old = hashmap_get(context->set_credentials, k);
+        if (old) {
+                free_and_replace(old->data, unescaped);
+                old->size = l;
+        } else {
+                _cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
+
+                sc = new0(ExecSetCredential, 1);
+                if (!sc)
+                        return log_oom();
+
+                sc->id = TAKE_PTR(k);
+                sc->data = TAKE_PTR(unescaped);
+                sc->size = l;
+
+                r = hashmap_ensure_allocated(&context->set_credentials, &exec_set_credential_hash_ops);
+                if (r < 0)
+                        return r;
+
+                r = hashmap_put(context->set_credentials, sc->id, sc);
+                if (r < 0)
+                        return log_oom();
+
+                TAKE_PTR(sc);
+        }
+
+        return 0;
+}
+
+int config_parse_load_credential(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_free_ char *word = NULL, *k = NULL, *q = NULL;
+        ExecContext *context = data;
+        Unit *u = userdata;
+        const char *p;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(context);
+
+        if (isempty(rvalue)) {
+                /* Empty assignment resets the list */
+                context->load_credentials = strv_free(context->load_credentials);
+                return 0;
+        }
+
+        p = rvalue;
+        r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r <= 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        r = unit_full_printf(u, word, &k);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
+                return 0;
+        }
+        if (!credential_name_valid(k)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", k);
+                return 0;
+        }
+        r = unit_full_printf(u, p, &q);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", p);
+                return 0;
+        }
+        if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Credential source \"%s\" not valid, ignoring.", q);
+                return 0;
+        }
+
+        r = strv_consume_pair(&context->load_credentials, TAKE_PTR(k), TAKE_PTR(q));
+        if (r < 0)
+                return log_oom();
+
+        return 0;
+}
+
 int config_parse_set_status(
                 const char *unit,
                 const char *filename,
index ae134610b1e149d6def0d77624031e2b8f373365..e90953b80f6e6bb84775418b2fabee79eba99269 100644 (file)
@@ -90,6 +90,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
 CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
 CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode);
 CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
+CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
+CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
 CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
 CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
 CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
index b7ca184081867401c18ddb7ffce6f1c95c8ada7f..49bf5419d8ee19d40b38e383414f137b1ff50a37 100644 (file)
@@ -591,6 +591,7 @@ static char** sanitize_environment(char **l) {
                         l,
                         "CACHE_DIRECTORY",
                         "CONFIGURATION_DIRECTORY",
+                        "CREDENTIALS_DIRECTORY",
                         "EXIT_CODE",
                         "EXIT_STATUS",
                         "INVOCATION_ID",
@@ -754,6 +755,7 @@ static int manager_setup_sigchld_event_source(Manager *m) {
 
 int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager **_m) {
         _cleanup_(manager_freep) Manager *m = NULL;
+        const char *e;
         int r;
 
         assert(_m);
@@ -857,6 +859,13 @@ int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager
         if (r < 0)
                 return r;
 
+        e = secure_getenv("CREDENTIALS_DIRECTORY");
+        if (e) {
+                m->received_credentials = strdup(e);
+                if (!m->received_credentials)
+                        return -ENOMEM;
+        }
+
         r = sd_event_default(&m->event);
         if (r < 0)
                 return r;
@@ -1420,6 +1429,7 @@ Manager* manager_free(Manager *m) {
 
         for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
                 m->prefix[dt] = mfree(m->prefix[dt]);
+        free(m->received_credentials);
 
         return mfree(m);
 }
index 81b0c13a955a7bcbbb56cc655ce74eaf24215e0f..d507dc1f3b49f2dfe53ebd2590b99c871c17c4f8 100644 (file)
@@ -424,6 +424,7 @@ struct Manager {
 
         /* Prefixes of e.g. RuntimeDirectory= */
         char *prefix[_EXEC_DIRECTORY_TYPE_MAX];
+        char *received_credentials;
 
         /* Used in the SIGCHLD and sd_notify() message invocation logic to avoid that we dispatch the same event
          * multiple times on the same unit. */
index fa951085238f7f3684decbf9b5f2417e3eb8f659..72a00f1955b1be24f70c52251db2393045d7ad30 100644 (file)
@@ -178,7 +178,8 @@ libcore = static_library(
                         libkmod,
                         libapparmor,
                         libselinux,
-                        libmount])
+                        libmount,
+                        libacl])
 
 systemd_sources = files('main.c')
 
index 337e94e90e40fab47ab4a166db38f9ae730098ee..6e9d61ff1888d4effbee7ec276319016a2e1c3e2 100644 (file)
@@ -869,7 +869,7 @@ static void mount_enter_dead(Mount *m, MountResult f) {
 
         m->exec_runtime = exec_runtime_unref(m->exec_runtime, true);
 
-        unit_destroy_runtime_directory(UNIT(m), &m->exec_context);
+        unit_destroy_runtime_data(UNIT(m), &m->exec_context);
 
         unit_unref_uid_gid(UNIT(m), true);
 
index 641391752a8642cf883ac890d60a22d6eb555b1e..dec6b2c9ea0e057d275ae0325fabb4a0e4d8a7ee 100644 (file)
@@ -1801,7 +1801,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
         s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
 
         /* Also, remove the runtime directory */
-        unit_destroy_runtime_directory(UNIT(s), &s->exec_context);
+        unit_destroy_runtime_data(UNIT(s), &s->exec_context);
 
         /* Get rid of the IPC bits of the user */
         unit_unref_uid_gid(UNIT(s), true);
@@ -2156,7 +2156,7 @@ static void service_enter_start(Service *s) {
         r = service_spawn(s,
                           c,
                           timeout,
-                          EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG,
+                          EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_WRITE_CREDENTIALS,
                           &pid);
         if (r < 0)
                 goto fail;
index 127195c9fe811814b3788adffae3f24357fdbbbf..0588a34e3860b6b107fddbcb77fbd12b39e7037d 100644 (file)
@@ -2080,7 +2080,7 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
 
         s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
 
-        unit_destroy_runtime_directory(UNIT(s), &s->exec_context);
+        unit_destroy_runtime_data(UNIT(s), &s->exec_context);
 
         unit_unref_uid_gid(UNIT(s), true);
 
index 20179de2d26df384bb43e7af27494dd4323ba1ba..83ae66c964dc6ceccbd865c6023e7a09380bba0e 100644 (file)
@@ -706,7 +706,7 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
 
         s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
 
-        unit_destroy_runtime_directory(UNIT(s), &s->exec_context);
+        unit_destroy_runtime_data(UNIT(s), &s->exec_context);
 
         unit_unref_uid_gid(UNIT(s), true);
 
index 0e66c132945f191d92281506240b5e310a02508b..e81ef2fd9c995354fb835f5f01dd426fe71eade7 100644 (file)
@@ -5429,6 +5429,8 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) {
         p->cgroup_path = u->cgroup_path;
         SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, unit_cgroup_delegate(u));
 
+        p->received_credentials = u->manager->received_credentials;
+
         return 0;
 }
 
@@ -6123,10 +6125,15 @@ int unit_test_trigger_loaded(Unit *u) {
         return 0;
 }
 
-void unit_destroy_runtime_directory(Unit *u, const ExecContext *context) {
+void unit_destroy_runtime_data(Unit *u, const ExecContext *context) {
+        assert(u);
+        assert(context);
+
         if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
             (context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u)))
                 exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
+
+        exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id);
 }
 
 int unit_clean(Unit *u, ExecCleanMask mask) {
index d5e4c65989e400922724efb333714b91bfa06e67..e217cec2191de50b1f283d74b6b1219a13727c91 100644 (file)
@@ -880,7 +880,7 @@ int unit_failure_action_exit_status(Unit *u);
 
 int unit_test_trigger_loaded(Unit *u);
 
-void unit_destroy_runtime_directory(Unit *u, const ExecContext *context);
+void unit_destroy_runtime_data(Unit *u, const ExecContext *context);
 int unit_clean(Unit *u, ExecCleanMask mask);
 int unit_can_clean(Unit *u, ExecCleanMask *ret_mask);
 
index d010d3bf3ecd02804066eab5d82fb166e3b432d4..2ad196e8244dd10758e693ac06f95dd7c62d16a2 100644 (file)
@@ -973,6 +973,117 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                 return 1;
         }
 
+        if (streq(field, "SetCredential")) {
+                r = sd_bus_message_open_container(m, 'r', "sv");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append_basic(m, 's', "SetCredential");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'v', "a(say)");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                if (isempty(eq))
+                        r = sd_bus_message_append(m, "a(say)", 0);
+                else {
+                        _cleanup_free_ char *word = NULL, *unescaped = NULL;
+                        const char *p = eq;
+                        int l;
+
+                        r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r == -ENOMEM)
+                                return log_oom();
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse SetCredential= parameter: %s", eq);
+                        if (r == 0 || !p)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to SetCredential=.");
+
+                        l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
+                        if (l < 0)
+                                return log_error_errno(l, "Failed to unescape SetCredential= value: %s", p);
+
+                        r = sd_bus_message_open_container(m, 'a', "(say)");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_open_container(m, 'r', "say");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_append(m, "s", word);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_append_array(m, 'y', unescaped, l);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_close_container(m);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_close_container(m);
+                }
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                return 1;
+        }
+
+        if (streq(field, "LoadCredential")) {
+                r = sd_bus_message_open_container(m, 'r', "sv");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append_basic(m, 's', "LoadCredential");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'v', "a(ss)");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                if (isempty(eq))
+                        r = sd_bus_message_append(m, "a(ss)", 0);
+                else {
+                        _cleanup_free_ char *word = NULL;
+                        const char *p = eq;
+
+                        r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r == -ENOMEM)
+                                return log_oom();
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse LoadCredential= parameter: %s", eq);
+                        if (r == 0 || !p)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to LoadCredential=.");
+
+                        r = sd_bus_message_append(m, "a(ss)", 1, word, p);
+                }
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                return 1;
+        }
+
         if (streq(field, "LogExtraFields")) {
                 r = sd_bus_message_open_container(m, 'r', "sv");
                 if (r < 0)
index 82b1422b2d16a8f08f769001298f158613e03a09..e2b6c679535f1e3e02362d4b0af65dd384a74878 100644 (file)
@@ -70,6 +70,8 @@ const ExitStatusMapping exit_status_mappings[256] = {
         [EXIT_LOGS_DIRECTORY] =          { "LOGS_DIRECTORY",          EXIT_STATUS_SYSTEMD },
         [EXIT_CONFIGURATION_DIRECTORY] = { "CONFIGURATION_DIRECTORY", EXIT_STATUS_SYSTEMD },
         [EXIT_NUMA_POLICY] =             { "NUMA_POLICY",             EXIT_STATUS_SYSTEMD },
+        [EXIT_CREDENTIALS] =             { "CREDENTIALS",             EXIT_STATUS_SYSTEMD },
+
         [EXIT_EXCEPTION] =               { "EXCEPTION",               EXIT_STATUS_SYSTEMD },
 
         [EXIT_INVALIDARGUMENT] =         { "INVALIDARGUMENT",         EXIT_STATUS_LSB },
index 9ea147c84239bcd8b564def438d0bb9fe8a1f8f1..7ac99a481083b9caaedebc6addb6e4255a708dac 100644 (file)
@@ -70,6 +70,7 @@ enum {
         EXIT_LOGS_DIRECTORY, /* 240 */
         EXIT_CONFIGURATION_DIRECTORY,
         EXIT_NUMA_POLICY,
+        EXIT_CREDENTIALS,
 
         EXIT_EXCEPTION = 255,  /* Whenever we want to propagate an abnormal/signal exit, in line with bash */
 };