Blob Blame History Raw
From aafa651e44df825abeec061f295f227862aad6d9 Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Thu, 30 Aug 2018 08:45:11 +0000
Subject: [PATCH] cryptsetup-generator: introduce basic keydev support

Dracut has a support for unlocking encrypted drives with keyfile stored
on the external drive. This support is included in the generated initrd
only if systemd module is not included.

When systemd is used in initrd then attachment of encrypted drives is
handled by systemd-cryptsetup tools. Our generator has support for
keyfile, however, it didn't support keyfile on the external block
device (keydev).

This commit introduces basic keydev support. Keydev can be specified per
luks.uuid on the kernel command line. Keydev is automatically mounted
during boot and we look for keyfile in the keydev
mountpoint (i.e. keyfile path is prefixed with the keydev mount point
path). After crypt device is attached we automatically unmount
where keyfile resides.

Example:
        rd.luks.key=70bc876b-f627-4038-9049-3080d79d2165=/key:LABEL=KEYDEV

(cherry-picked from commit 70f5f48eb891b12e969577b464de61e15a2593da)

Resolves: #1619743
---
 man/systemd-cryptsetup-generator.xml  |  14 +++
 src/cryptsetup/cryptsetup-generator.c | 122 ++++++++++++++++++++++++--
 2 files changed, 131 insertions(+), 5 deletions(-)

diff --git a/man/systemd-cryptsetup-generator.xml b/man/systemd-cryptsetup-generator.xml
index b6270358e..8cfd8b6a8 100644
--- a/man/systemd-cryptsetup-generator.xml
+++ b/man/systemd-cryptsetup-generator.xml
@@ -168,6 +168,20 @@
         to the one specified by <varname>rd.luks.key=</varname> or
         <varname>luks.key=</varname> of the corresponding UUID, or the
         password file that was specified without a UUID.</para>
+
+        <para>It is also possible to specify an external device which
+        should be mounted before we attempt to unlock the LUKS device.
+        systemd-cryptsetup will use password file stored on that
+        device. Device containing password file is specified by
+        appending colon and a device identifier to the password file
+        path. For example,
+        <varname>rd.luks.uuid=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40
+        <varname>rd.luks.key=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40=/keyfile:LABEL=keydev.
+        Hence, in this case, we will attempt to mount file system
+        residing on the block device with label <literal>keydev</literal>.
+        This syntax is for now only supported on a per-device basis,
+        i.e. you have to specify LUKS device UUID.</para>
+
         <para><varname>rd.luks.key=</varname>
         is honored only by initial RAM disk
         (initrd) while
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
index 5f29093f5..42c30c5ca 100644
--- a/src/cryptsetup/cryptsetup-generator.c
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -38,6 +38,7 @@
 typedef struct crypto_device {
         char *uuid;
         char *keyfile;
+        char *keydev;
         char *name;
         char *options;
         bool create;
@@ -51,14 +52,79 @@ static Hashmap *arg_disks = NULL;
 static char *arg_default_options = NULL;
 static char *arg_default_keyfile = NULL;
 
+static int generate_keydev_mount(const char *name, const char *keydev, char **unit, char **mount) {
+        _cleanup_free_ char *u = NULL, *what = NULL, *where = NULL, *p = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(name);
+        assert(keydev);
+        assert(unit);
+        assert(mount);
+
+        r = mkdir_parents("/run/systemd/cryptsetup", 0755);
+        if (r < 0)
+                return r;
+
+        r = mkdir("/run/systemd/cryptsetup", 0700);
+        if (r < 0)
+                return r;
+
+        where = strjoin("/run/systemd/cryptsetup/keydev-", name, NULL);
+        if (!where)
+                return -ENOMEM;
+
+        r = mkdir(where, 0700);
+        if (r < 0)
+                return r;
+
+        u = unit_name_from_path(where, ".mount");
+        if (!u)
+                return -ENOMEM;
+
+        what = fstab_node_to_udev_node(keydev);
+        if (!what)
+                return -ENOMEM;
+
+        p = strjoin(arg_dest, "/", u, NULL);
+        if (!p)
+                return log_oom();
+
+        f = fopen(p, "wxe");
+        if (!f)
+                return log_error_errno(errno, "Failed to create unit file %s: %m", p);
+
+        fprintf(f,
+                "# Automatically generated by systemd-cryptsetup-generator\n\n"
+                "[Unit]\n"
+                "DefaultDependencies=no\n\n"
+                "[Mount]\n"
+                "What=%s\n"
+                "Where=%s\n"
+                "Options=ro\n", what, where);
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                return r;
+
+        *unit = u;
+        u = NULL;
+
+        *mount = where;
+        where = NULL;
+
+        return 0;
+}
+
 static int create_disk(
                 const char *name,
                 const char *device,
+                const char *keydev,
                 const char *password,
                 const char *options) {
 
         _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *to = NULL, *e = NULL,
-                *filtered = NULL;
+                *filtered = NULL, *keydev_mount = NULL, *keyfile_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         bool noauto, nofail, tmp, swap, netdev;
         char *from;
@@ -98,6 +164,9 @@ static int create_disk(
         if (!d)
                 return log_oom();
 
+        if (keydev && !password)
+                return log_error_errno(-EINVAL, "Keydev is specified, but path to the password file is missing: %m");
+
         f = fopen(p, "wxe");
         if (!f)
                 return log_error_errno(errno, "Failed to create unit file %s: %m", p);
@@ -115,6 +184,20 @@ static int create_disk(
                 "After=%s\n",
                 netdev ? "remote-fs-pre.target" : "cryptsetup-pre.target");
 
+        if (keydev) {
+                _cleanup_free_ char *unit = NULL;
+
+                r = generate_keydev_mount(name, keydev, &unit, &keydev_mount);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to generate keydev mount unit: %m");
+
+                keyfile_path = prefix_root(keydev_mount, password);
+                if (!keyfile_path)
+                        return log_oom();
+
+                password = keyfile_path;
+        }
+
         if (!nofail)
                 fprintf(f,
                         "Before=%s\n",
@@ -181,6 +264,11 @@ static int create_disk(
                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
                         name);
 
+        if (keydev)
+                fprintf(f,
+                        "ExecStartPost=/bin/umount '%s'\n\n",
+                        keydev_mount);
+
         fflush(f);
         if (ferror(f))
                 return log_error_errno(errno, "Failed to write file %s: %m", p);
@@ -248,6 +336,7 @@ static void free_arg_disks(void) {
         while ((d = hashmap_steal_first(arg_disks))) {
                 free(d->uuid);
                 free(d->keyfile);
+                free(d->keydev);
                 free(d->name);
                 free(d->options);
                 free(d);
@@ -335,13 +424,36 @@ static int parse_proc_cmdline_item(const char *key, const char *value) {
 
                 r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
                 if (r == 2) {
+                        char *c;
+                        _cleanup_free_ char *keyfile = NULL, *keydev = NULL;
+
                         d = get_crypto_device(uuid);
                         if (!d)
                                 return log_oom();
 
+                        c = strrchr(uuid_value, ':');
+                        if (!c) {
+                                free(d->keyfile);
+                                d->keyfile = uuid_value;
+                                uuid_value = NULL;
+
+                                return 0;
+                        }
+
+                        *c = '\0';
+                        keyfile = strdup(uuid_value);
+                        keydev = strdup(++c);
+
+                        if (!keyfile || !keydev)
+                                return log_oom();
+
                         free(d->keyfile);
-                        d->keyfile = uuid_value;
-                        uuid_value = NULL;
+                        d->keyfile = keyfile;
+                        keyfile = NULL;
+
+                        free(d->keydev);
+                        d->keydev = keydev;
+                        keydev = NULL;
                 } else if (free_and_strdup(&arg_default_keyfile, value))
                         return log_oom();
 
@@ -420,7 +532,7 @@ static int add_crypttab_devices(void) {
                         continue;
                 }
 
-                r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options);
+                r = create_disk(name, device, NULL, keyfile, (d && d->options) ? d->options : options);
                 if (r < 0)
                         return r;
 
@@ -460,7 +572,7 @@ static int add_proc_cmdline_devices(void) {
                 else
                         options = "timeout=0";
 
-                r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, options);
+                r = create_disk(d->name, device, d->keydev, d->keyfile ?: arg_default_keyfile, options);
                 if (r < 0)
                         return r;
         }