ff6046
From 0977e6b34fb5f28fc94f1df32261742881fa9bbe Mon Sep 17 00:00:00 2001
ff6046
From: Michal Sekletar <msekleta@redhat.com>
ff6046
Date: Thu, 30 Aug 2018 08:45:11 +0000
ff6046
Subject: [PATCH] cryptsetup-generator: introduce basic keydev support
ff6046
ff6046
Dracut has a support for unlocking encrypted drives with keyfile stored
ff6046
on the external drive. This support is included in the generated initrd
ff6046
only if systemd module is not included.
ff6046
ff6046
When systemd is used in initrd then attachment of encrypted drives is
ff6046
handled by systemd-cryptsetup tools. Our generator has support for
ff6046
keyfile, however, it didn't support keyfile on the external block
ff6046
device (keydev).
ff6046
ff6046
This commit introduces basic keydev support. Keydev can be specified per
ff6046
luks.uuid on the kernel command line. Keydev is automatically mounted
ff6046
during boot and we look for keyfile in the keydev
ff6046
mountpoint (i.e. keyfile path is prefixed with the keydev mount point
ff6046
path). After crypt device is attached we automatically unmount
ff6046
where keyfile resides.
ff6046
ff6046
Example:
ff6046
        rd.luks.key=70bc876b-f627-4038-9049-3080d79d2165=/key:LABEL=KEYDEV
ff6046
ff6046
(cherry-picked from commit 70f5f48eb891b12e969577b464de61e15a2593da)
ff6046
ff6046
Resolves: #1656869
ff6046
---
ff6046
 man/systemd-cryptsetup-generator.xml  |  14 ++++
ff6046
 src/cryptsetup/cryptsetup-generator.c | 105 +++++++++++++++++++++++++-
ff6046
 2 files changed, 115 insertions(+), 4 deletions(-)
ff6046
ff6046
diff --git a/man/systemd-cryptsetup-generator.xml b/man/systemd-cryptsetup-generator.xml
ff6046
index c37ee76b87..e30d69bfe7 100644
ff6046
--- a/man/systemd-cryptsetup-generator.xml
ff6046
+++ b/man/systemd-cryptsetup-generator.xml
ff6046
@@ -144,6 +144,20 @@
ff6046
         to the one specified by <varname>rd.luks.key=</varname> or
ff6046
         <varname>luks.key=</varname> of the corresponding UUID, or the
ff6046
         password file that was specified without a UUID.</para>
ff6046
+
ff6046
+        <para>It is also possible to specify an external device which
ff6046
+        should be mounted before we attempt to unlock the LUKS device.
ff6046
+        systemd-cryptsetup will use password file stored on that
ff6046
+        device. Device containing password file is specified by
ff6046
+        appending colon and a device identifier to the password file
ff6046
+        path. For example,
ff6046
+        <varname>rd.luks.uuid=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40
ff6046
+        <varname>rd.luks.key=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40=/keyfile:LABEL=keydev.
ff6046
+        Hence, in this case, we will attempt to mount file system
ff6046
+        residing on the block device with label <literal>keydev</literal>.
ff6046
+        This syntax is for now only supported on a per-device basis,
ff6046
+        i.e. you have to specify LUKS device UUID.</para>
ff6046
+
ff6046
         <para><varname>rd.luks.key=</varname>
ff6046
         is honored only by initial RAM disk
ff6046
         (initrd) while
ff6046
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
ff6046
index f5a81829b9..8c7a76e789 100644
ff6046
--- a/src/cryptsetup/cryptsetup-generator.c
ff6046
+++ b/src/cryptsetup/cryptsetup-generator.c
ff6046
@@ -24,6 +24,7 @@
ff6046
 typedef struct crypto_device {
ff6046
         char *uuid;
ff6046
         char *keyfile;
ff6046
+        char *keydev;
ff6046
         char *name;
ff6046
         char *options;
ff6046
         bool create;
ff6046
@@ -37,14 +38,71 @@ static Hashmap *arg_disks = NULL;
ff6046
 static char *arg_default_options = NULL;
ff6046
 static char *arg_default_keyfile = NULL;
ff6046
 
ff6046
+static int generate_keydev_mount(const char *name, const char *keydev, char **unit, char **mount) {
ff6046
+        _cleanup_free_ char *u = NULL, *what = NULL, *where = NULL;
ff6046
+        _cleanup_fclose_ FILE *f = NULL;
ff6046
+        int r;
ff6046
+
ff6046
+        assert(name);
ff6046
+        assert(keydev);
ff6046
+        assert(unit);
ff6046
+        assert(mount);
ff6046
+
ff6046
+        r = mkdir_parents("/run/systemd/cryptsetup", 0755);
ff6046
+        if (r < 0)
ff6046
+                return r;
ff6046
+
ff6046
+        r = mkdir("/run/systemd/cryptsetup", 0700);
ff6046
+        if (r < 0)
ff6046
+                return r;
ff6046
+
ff6046
+        where = strjoin("/run/systemd/cryptsetup/keydev-", name);
ff6046
+        if (!where)
ff6046
+                return -ENOMEM;
ff6046
+
ff6046
+        r = mkdir(where, 0700);
ff6046
+        if (r < 0)
ff6046
+                return r;
ff6046
+
ff6046
+        r = unit_name_from_path(where, ".mount", &u);
ff6046
+        if (r < 0)
ff6046
+                return r;
ff6046
+
ff6046
+        r = generator_open_unit_file(arg_dest, NULL, u, &f);
ff6046
+        if (r < 0)
ff6046
+                return r;
ff6046
+
ff6046
+        what = fstab_node_to_udev_node(keydev);
ff6046
+        if (!what)
ff6046
+                return -ENOMEM;
ff6046
+
ff6046
+        fprintf(f,
ff6046
+                "[Unit]\n"
ff6046
+                "DefaultDependencies=no\n\n"
ff6046
+                "[Mount]\n"
ff6046
+                "What=%s\n"
ff6046
+                "Where=%s\n"
ff6046
+                "Options=ro\n", what, where);
ff6046
+
ff6046
+        r = fflush_and_check(f);
ff6046
+        if (r < 0)
ff6046
+                return r;
ff6046
+
ff6046
+        *unit = TAKE_PTR(u);
ff6046
+        *mount = TAKE_PTR(where);
ff6046
+
ff6046
+        return 0;
ff6046
+}
ff6046
+
ff6046
 static int create_disk(
ff6046
                 const char *name,
ff6046
                 const char *device,
ff6046
+                const char *keydev,
ff6046
                 const char *password,
ff6046
                 const char *options) {
ff6046
 
ff6046
         _cleanup_free_ char *n = NULL, *d = NULL, *u = NULL, *e = NULL,
ff6046
-                *filtered = NULL, *u_escaped = NULL, *password_escaped = NULL, *filtered_escaped = NULL, *name_escaped = NULL;
ff6046
+                *filtered = NULL, *u_escaped = NULL, *password_escaped = NULL, *filtered_escaped = NULL, *name_escaped = NULL, *keydev_mount = NULL;
ff6046
         _cleanup_fclose_ FILE *f = NULL;
ff6046
         const char *dmname;
ff6046
         bool noauto, nofail, tmp, swap, netdev;
ff6046
@@ -94,6 +152,9 @@ static int create_disk(
ff6046
                         return log_oom();
ff6046
         }
ff6046
 
ff6046
+        if (keydev && !password)
ff6046
+                return log_error_errno(-EINVAL, "Keydev is specified, but path to the password file is missing: %m");
ff6046
+
ff6046
         r = generator_open_unit_file(arg_dest, NULL, n, &f);
ff6046
         if (r < 0)
ff6046
                 return r;
ff6046
@@ -109,6 +170,20 @@ static int create_disk(
ff6046
                 "After=%s\n",
ff6046
                 netdev ? "remote-fs-pre.target" : "cryptsetup-pre.target");
ff6046
 
ff6046
+        if (keydev) {
ff6046
+                _cleanup_free_ char *unit = NULL, *p = NULL;
ff6046
+
ff6046
+                r = generate_keydev_mount(name, keydev, &unit, &keydev_mount);
ff6046
+                if (r < 0)
ff6046
+                        return log_error_errno(r, "Failed to generate keydev mount unit: %m");
ff6046
+
ff6046
+                p = prefix_root(keydev_mount, password_escaped);
ff6046
+                if (!p)
ff6046
+                        return log_oom();
ff6046
+
ff6046
+                free_and_replace(password_escaped, p);
ff6046
+        }
ff6046
+
ff6046
         if (!nofail)
ff6046
                 fprintf(f,
ff6046
                         "Before=%s\n",
ff6046
@@ -186,6 +261,11 @@ static int create_disk(
ff6046
                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
ff6046
                         name_escaped);
ff6046
 
ff6046
+        if (keydev)
ff6046
+                fprintf(f,
ff6046
+                        "ExecStartPost=" UMOUNT_PATH " %s\n\n",
ff6046
+                        keydev_mount);
ff6046
+
ff6046
         r = fflush_and_check(f);
ff6046
         if (r < 0)
ff6046
                 return log_error_errno(r, "Failed to write unit file %s: %m", n);
ff6046
@@ -221,6 +301,7 @@ static int create_disk(
ff6046
 static void crypt_device_free(crypto_device *d) {
ff6046
         free(d->uuid);
ff6046
         free(d->keyfile);
ff6046
+        free(d->keydev);
ff6046
         free(d->name);
ff6046
         free(d->options);
ff6046
         free(d);
ff6046
@@ -309,11 +390,27 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
ff6046
 
ff6046
                 r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
ff6046
                 if (r == 2) {
ff6046
+                        char *c;
ff6046
+                        _cleanup_free_ char *keyfile = NULL, *keydev = NULL;
ff6046
+
ff6046
                         d = get_crypto_device(uuid);
ff6046
                         if (!d)
ff6046
                                 return log_oom();
ff6046
 
ff6046
-                        free_and_replace(d->keyfile, uuid_value);
ff6046
+                        c = strrchr(uuid_value, ':');
ff6046
+                        if (!c)
ff6046
+                                /* No keydev specified */
ff6046
+                                return free_and_replace(d->keyfile, uuid_value);
ff6046
+
ff6046
+                        *c = '\0';
ff6046
+                        keyfile = strdup(uuid_value);
ff6046
+                        keydev = strdup(++c);
ff6046
+
ff6046
+                        if (!keyfile || !keydev)
ff6046
+                                return log_oom();
ff6046
+
ff6046
+                        free_and_replace(d->keyfile, keyfile);
ff6046
+                        free_and_replace(d->keydev, keydev);
ff6046
                 } else if (free_and_strdup(&arg_default_keyfile, value) < 0)
ff6046
                         return log_oom();
ff6046
 
ff6046
@@ -394,7 +491,7 @@ static int add_crypttab_devices(void) {
ff6046
                         continue;
ff6046
                 }
ff6046
 
ff6046
-                r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options);
ff6046
+                r = create_disk(name, device, NULL, keyfile, (d && d->options) ? d->options : options);
ff6046
                 if (r < 0)
ff6046
                         return r;
ff6046
 
ff6046
@@ -434,7 +531,7 @@ static int add_proc_cmdline_devices(void) {
ff6046
                 else
ff6046
                         options = "timeout=0";
ff6046
 
ff6046
-                r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, options);
ff6046
+                r = create_disk(d->name, device, d->keydev, d->keyfile ?: arg_default_keyfile, options);
ff6046
                 if (r < 0)
ff6046
                         return r;
ff6046
         }