]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: add --set-credential= and --load-credential=
authorLennart Poettering <lennart@poettering.net>
Thu, 23 Jul 2020 06:47:08 +0000 (08:47 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 25 Aug 2020 17:45:47 +0000 (19:45 +0200)
Let's allow passing in creds to containers, so that PID 1 inside the
container can pick them up.

src/nspawn/meson.build
src/nspawn/nspawn-creds.c [new file with mode: 0644]
src/nspawn/nspawn-creds.h [new file with mode: 0644]
src/nspawn/nspawn-settings.h
src/nspawn/nspawn.c

index c049ac6754113eee3fe2e12ec6ad07329f0b04bd..ae3d72faca0d803800a6ccc78dfa1f2b8f0cbb8f 100644 (file)
@@ -3,6 +3,8 @@
 libnspawn_core_sources = files('''
         nspawn-cgroup.c
         nspawn-cgroup.h
+        nspawn-creds.c
+        nspawn-creds.h
         nspawn-def.h
         nspawn-expose-ports.c
         nspawn-expose-ports.h
diff --git a/src/nspawn/nspawn-creds.c b/src/nspawn/nspawn-creds.c
new file mode 100644 (file)
index 0000000..41a38d3
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "nspawn-creds.h"
+
+static void credential_free(Credential *cred) {
+        assert(cred);
+
+        cred->id = mfree(cred->id);
+        cred->data = erase_and_free(cred->data);
+        cred->size = 0;
+}
+
+void credential_free_all(Credential *creds, size_t n) {
+        size_t i;
+
+        assert(creds || n == 0);
+
+        for (i = 0; i < n; i++)
+                credential_free(creds + i);
+
+        free(creds);
+}
diff --git a/src/nspawn/nspawn-creds.h b/src/nspawn/nspawn-creds.h
new file mode 100644 (file)
index 0000000..b3c90bb
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <sys/types.h>
+
+typedef struct Credential {
+        char *id;
+        void *data;
+        size_t size;
+} Credential;
+
+void credential_free_all(Credential *creds, size_t n);
index ab31c05a9e0e6b92bf512786f206717b3780c248..b8fa145f77bba447af1faade37e5565cde8904be 100644 (file)
@@ -116,9 +116,10 @@ typedef enum SettingsMask {
         SETTING_USE_CGNS          = UINT64_C(1) << 27,
         SETTING_CLONE_NS_FLAGS    = UINT64_C(1) << 28,
         SETTING_CONSOLE_MODE      = UINT64_C(1) << 29,
-        SETTING_RLIMIT_FIRST      = UINT64_C(1) << 30, /* we define one bit per resource limit here */
-        SETTING_RLIMIT_LAST       = UINT64_C(1) << (30 + _RLIMIT_MAX - 1),
-        _SETTINGS_MASK_ALL        = (UINT64_C(1) << (30 + _RLIMIT_MAX)) -1,
+        SETTING_CREDENTIALS       = UINT64_C(1) << 30,
+        SETTING_RLIMIT_FIRST      = UINT64_C(1) << 31, /* we define one bit per resource limit here */
+        SETTING_RLIMIT_LAST       = UINT64_C(1) << (31 + _RLIMIT_MAX - 1),
+        _SETTINGS_MASK_ALL        = (UINT64_C(1) << (31 + _RLIMIT_MAX)) -1,
         _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
 } SettingsMask;
 
index 3ad882985532aac7ed0318d01ae044737fe09aba..26469759d907466d7b939a7da69e7a554153c811 100644 (file)
@@ -36,6 +36,7 @@
 #include "dev-setup.h"
 #include "dissect-image.h"
 #include "env-util.h"
+#include "escape.h"
 #include "fd-util.h"
 #include "fdset.h"
 #include "fileio.h"
@@ -45,6 +46,7 @@
 #include "hexdecoct.h"
 #include "hostname-util.h"
 #include "id128-util.h"
+#include "io-util.h"
 #include "log.h"
 #include "loop-util.h"
 #include "loopback-setup.h"
@@ -58,6 +60,7 @@
 #include "namespace-util.h"
 #include "netlink-util.h"
 #include "nspawn-cgroup.h"
+#include "nspawn-creds.h"
 #include "nspawn-def.h"
 #include "nspawn-expose-ports.h"
 #include "nspawn-mount.h"
@@ -219,6 +222,8 @@ static DeviceNode* arg_extra_nodes = NULL;
 static size_t arg_n_extra_nodes = 0;
 static char **arg_sysctl = NULL;
 static ConsoleMode arg_console_mode = _CONSOLE_MODE_INVALID;
+static Credential *arg_credentials = NULL;
+static size_t arg_n_credentials = 0;
 
 STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
@@ -406,7 +411,13 @@ static int help(void) {
                "%3$sInput/Output:%4$s\n"
                "     --console=MODE         Select how stdin/stdout/stderr and /dev/console are\n"
                "                            set up for the container.\n"
-               "  -P --pipe                 Equivalent to --console=pipe\n"
+               "  -P --pipe                 Equivalent to --console=pipe\n\n"
+               "%3$sCredentials:%4$s\n"
+               "     --set-credential=ID:VALUE\n"
+               "                            Pass a credential with literal value to container.\n"
+               "     --load-credential=ID:PATH\n"
+               "                            Load credential to pass to container from file or\n"
+               "                            AF_UNIX stream socket.\n"
                "\nSee the %2$s for details.\n"
                , program_invocation_short_name
                , link
@@ -675,6 +686,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_PAGER,
                 ARG_VERITY_DATA,
                 ARG_ROOT_HASH_SIG,
+                ARG_SET_CREDENTIAL,
+                ARG_LOAD_CREDENTIAL,
         };
 
         static const struct option options[] = {
@@ -742,6 +755,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "no-pager",               no_argument,       NULL, ARG_NO_PAGER               },
                 { "verity-data",            required_argument, NULL, ARG_VERITY_DATA            },
                 { "root-hash-sig",          required_argument, NULL, ARG_ROOT_HASH_SIG          },
+                { "set-credential",         required_argument, NULL, ARG_SET_CREDENTIAL         },
+                { "load-credential",        required_argument, NULL, ARG_LOAD_CREDENTIAL        },
                 {}
         };
 
@@ -1496,6 +1511,105 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_pager_flags |= PAGER_DISABLE;
                         break;
 
+                case ARG_SET_CREDENTIAL: {
+                        _cleanup_free_ char *word = NULL, *data = NULL;
+                        const char *p = optarg;
+                        Credential *a;
+                        size_t i;
+                        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 --set-credential= parameter: %m");
+                        if (r == 0 || !p)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", optarg);
+
+                        if (!credential_name_valid(word))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word);
+
+                        for (i = 0; i < arg_n_credentials; i++)
+                                if (streq(arg_credentials[i].id, word))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word);
+
+                        l = cunescape(p, UNESCAPE_ACCEPT_NUL, &data);
+                        if (l < 0)
+                                return log_error_errno(l, "Failed to unescape credential data: %s", p);
+
+                        a = reallocarray(arg_credentials, arg_n_credentials + 1, sizeof(Credential));
+                        if (!a)
+                                return log_oom();
+
+                        a[arg_n_credentials++] = (Credential) {
+                                .id = TAKE_PTR(word),
+                                .data = TAKE_PTR(data),
+                                .size = l,
+                        };
+
+                        arg_credentials = a;
+
+                        arg_settings_mask |= SETTING_CREDENTIALS;
+                        break;
+                }
+
+                case ARG_LOAD_CREDENTIAL: {
+                        ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
+                        _cleanup_(erase_and_freep) char *data = NULL;
+                        _cleanup_free_ char *word = NULL, *j = NULL;
+                        const char *p = optarg;
+                        Credential *a;
+                        size_t size, i;
+
+                        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 --set-credential= parameter: %m");
+                        if (r == 0 || !p)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", optarg);
+
+                        if (!credential_name_valid(word))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word);
+
+                        for (i = 0; i < arg_n_credentials; i++)
+                                if (streq(arg_credentials[i].id, word))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word);
+
+                        if (path_is_absolute(p))
+                                flags |= READ_FULL_FILE_CONNECT_SOCKET;
+                        else {
+                                const char *e;
+
+                                e = getenv("CREDENTIALS_DIRECTORY");
+                                if (!e)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential not available (no credentials passed at all): %s", word);
+
+                                j = path_join(e, p);
+                                if (!j)
+                                        return log_oom();
+                        }
+
+                        r = read_full_file_full(AT_FDCWD, j ?: p, flags, &data, &size);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to read credential '%s': %m", j ?: p);
+
+                        a = reallocarray(arg_credentials, arg_n_credentials + 1, sizeof(Credential));
+                        if (!a)
+                                return log_oom();
+
+                        a[arg_n_credentials++] = (Credential) {
+                                .id = TAKE_PTR(word),
+                                .data = TAKE_PTR(data),
+                                .size = size,
+                        };
+
+                        arg_credentials = a;
+
+                        arg_settings_mask |= SETTING_CREDENTIALS;
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -2228,6 +2342,66 @@ static int setup_keyring(void) {
         return 0;
 }
 
+static int setup_credentials(const char *root) {
+        const char *q;
+        int r;
+
+        if (arg_n_credentials <= 0)
+                return 0;
+
+        r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create /run/host: %m");
+
+        r = userns_mkdir(root, "/run/host/credentials", 0700, 0, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create /run/host/credentials: %m");
+
+        q = prefix_roota(root, "/run/host/credentials");
+        r = mount_verbose(LOG_ERR, NULL, q, "ramfs", MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0700");
+        if (r < 0)
+                return r;
+
+        for (size_t i = 0; i < arg_n_credentials; i++) {
+                _cleanup_free_ char *j = NULL;
+                _cleanup_close_ int fd = -1;
+
+                j = path_join(q, arg_credentials[i].id);
+                if (!j)
+                        return log_oom();
+
+                fd = open(j, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOFOLLOW, 0600);
+                if (fd < 0)
+                        return log_error_errno(errno, "Failed to create credential file %s: %m", j);
+
+                r = loop_write(fd, arg_credentials[i].data, arg_credentials[i].size, /* do_poll= */ false);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to write credential to file %s: %m", j);
+
+                if (fchmod(fd, 0400) < 0)
+                        return log_error_errno(errno, "Failed to adjust access mode of %s: %m", j);
+
+                if (arg_userns_mode != USER_NAMESPACE_NO) {
+                        if (fchown(fd, arg_uid_shift, arg_uid_shift) < 0)
+                                return log_error_errno(errno, "Failed to adjust ownership of %s: %m", j);
+                }
+        }
+
+        if (chmod(q, 0500) < 0)
+                return log_error_errno(errno, "Failed to adjust access mode of %s: %m", q);
+
+        r = userns_lchown(q, 0, 0);
+        if (r < 0)
+                return r;
+
+        /* Make both mount and superblock read-only now */
+        r = mount_verbose(LOG_ERR, NULL, q, NULL, MS_REMOUNT|MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
+        if (r < 0)
+                return r;
+
+        return mount_verbose(LOG_ERR, NULL, q, NULL, MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0500");
+}
+
 static int setup_kmsg(int kmsg_socket) {
         _cleanup_(unlink_and_freep) char *from = NULL;
         _cleanup_free_ char *fifo = NULL;
@@ -2941,6 +3115,7 @@ static int inner_child(
                 NULL, /* LISTEN_FDS */
                 NULL, /* LISTEN_PID */
                 NULL, /* NOTIFY_SOCKET */
+                NULL, /* CREDENTIALS_DIRECTORY */
                 NULL
         };
         const char *exec_target;
@@ -3191,6 +3366,13 @@ static int inner_child(
         if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0)
                 return log_oom();
 
+        if (arg_n_credentials > 0) {
+                envp[n_env] = strdup("CREDENTIALS_DIRECTORY=/run/host/credentials");
+                if (!envp[n_env])
+                        return log_oom();
+                n_env++;
+        }
+
         env_use = strv_env_merge(3, envp, os_release_pairs, arg_setenv);
         if (!env_use)
                 return log_oom();
@@ -3538,6 +3720,10 @@ static int outer_child(
         if (r < 0)
                 return r;
 
+        r = setup_credentials(directory);
+        if (r < 0)
+                return r;
+
         r = mount_custom(
                         directory,
                         arg_custom_mounts,
@@ -5339,6 +5525,7 @@ finish:
         expose_port_free_all(arg_expose_ports);
         rlimit_free_all(arg_rlimit);
         device_node_array_free(arg_extra_nodes, arg_n_extra_nodes);
+        credential_free_all(arg_credentials, arg_n_credentials);
 
         if (r < 0)
                 return r;