mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-25 06:03:40 +03:00
cryptsetup-generator: add detached LUKS header support
Adds support for LUKS detached header device on kernel command line. It's introduced via extension to existing luks.options 'header=' argument beyond colon (see examples below). If LUKS header device is specified it's expected to contain filesystem with LUKS header image on a path specified in the first part of header specification. The second parameter 'luks.data' specifies LUKS data device supposed to be paired with detached LUKS header (note that encrypted LUKS data device with detached header is unrecognisable by standard blkid probe). This adds support for LUKS encrypted rootfs partition with detached header. It can also be used for initializing online LUKS2 encryption of data device. Examples: luks.data=<luks_uuid>=/dev/sdz luks.data=<luks_uuid>=/dev/vg/lv luks.data=<luks_uuid>=/dev/mapper/lv luks.data=<luks_uuid>=PARTUUID=<part_uuid> luks.data=<luks_uuid>=PARTLABEL=<part_uuid> luks.options=<luks_uuid>=header=/header/path:UUID=<fs_uuid> luks.options=<luks_uuid>=header=/header/path:PARTUUID=<part_uuid> luks.options=<luks_uuid>=header=/header/path:PARTLABEL=<part_label> luks.options=<luks_uuid>=header=/header/path:LABEL=<fs_label> luks.options=<luks_uuid>=header=/header/path:/dev/sdx luks.options=<luks_uuid>=header=/header/path:/dev/vg/lv The '/header/path' is considered to be relative location within filesystem residing on the header device specified beyond colon character
This commit is contained in:
parent
fc6f1ad121
commit
a8574d0055
@ -105,6 +105,9 @@
|
|||||||
LUKS device given by the UUID appear under the provided
|
LUKS device given by the UUID appear under the provided
|
||||||
name.</para>
|
name.</para>
|
||||||
|
|
||||||
|
<para>This parameter is the analogue of the first <citerefentry><refentrytitle>crypttab</refentrytitle>
|
||||||
|
<manvolnum>5</manvolnum></citerefentry> field <replaceable>volume-name</replaceable>.</para>
|
||||||
|
|
||||||
<para><varname>rd.luks.name=</varname> is honored only by
|
<para><varname>rd.luks.name=</varname> is honored only by
|
||||||
initial RAM disk (initrd) while <varname>luks.name=</varname>
|
initial RAM disk (initrd) while <varname>luks.name=</varname>
|
||||||
is honored by both the main system and the initrd.</para>
|
is honored by both the main system and the initrd.</para>
|
||||||
@ -112,20 +115,30 @@
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>luks.options=</varname></term>
|
<term><varname>luks.data=</varname></term>
|
||||||
<term><varname>rd.luks.options=</varname></term>
|
<term><varname>rd.luks.data=</varname></term>
|
||||||
|
|
||||||
<listitem><para>Takes a LUKS super block UUID followed by an
|
<listitem><para>Takes a LUKS super block UUID followed by a <literal>=</literal> and a block device
|
||||||
<literal>=</literal> and a string of options separated by
|
specification for device hosting encrypted data.</para>
|
||||||
commas as argument. This will override the options for the
|
|
||||||
given UUID.</para>
|
<para>For those entries specified with <varname>rd.luks.uuid=</varname> or
|
||||||
<para>If only a list of options, without an UUID, is
|
<varname>luks.uuid=</varname>, the data device will be set to the one specified by
|
||||||
specified, they apply to any UUIDs not specified elsewhere,
|
<varname>rd.luks.data=</varname> or <varname>luks.data=</varname> of the corresponding UUID.</para>
|
||||||
and without an entry in
|
|
||||||
<filename>/etc/crypttab</filename>.</para><para>
|
<para>LUKS data device parameter is usefull for specifying encrypted data devices with detached headers specified in
|
||||||
<varname>rd.luks.options=</varname> is honored only by initial
|
<varname>luks.options</varname> entry containing <literal>header=</literal> argument. For example,
|
||||||
RAM disk (initrd) while <varname>luks.options=</varname> is
|
<varname>rd.luks.uuid=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40
|
||||||
honored by both the main system and the initrd.</para>
|
<varname>rd.luks.options=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40=header=/path/to/luks.hdr
|
||||||
|
<varname>rd.luks.data=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40=/dev/sdx.
|
||||||
|
Hence, in this case, we will attempt to unlock LUKS device assembled from data device <literal>/dev/sdx</literal>
|
||||||
|
and LUKS header (metadata) put in <literal>/path/to/luks.hdr</literal> file. This syntax is for now
|
||||||
|
only supported on a per-device basis, i.e. you have to specify LUKS device UUID.</para>
|
||||||
|
|
||||||
|
<para>This parameter is the analogue of the second <citerefentry><refentrytitle>crypttab</refentrytitle>
|
||||||
|
<manvolnum>5</manvolnum></citerefentry> field <replaceable>encrypted-device</replaceable>.</para>
|
||||||
|
|
||||||
|
<para><varname>rd.luks.data=</varname> is honored only by initial RAM disk (initrd) while
|
||||||
|
<varname>luks.data=</varname> is honored by both the main system and the initrd.</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
@ -157,6 +170,9 @@
|
|||||||
This syntax is for now only supported on a per-device basis,
|
This syntax is for now only supported on a per-device basis,
|
||||||
i.e. you have to specify LUKS device UUID.</para>
|
i.e. you have to specify LUKS device UUID.</para>
|
||||||
|
|
||||||
|
<para>This parameter is the analogue of the third <citerefentry><refentrytitle>crypttab</refentrytitle>
|
||||||
|
<manvolnum>5</manvolnum></citerefentry> field <replaceable>key-file</replaceable>.</para>
|
||||||
|
|
||||||
<para><varname>rd.luks.key=</varname>
|
<para><varname>rd.luks.key=</varname>
|
||||||
is honored only by initial RAM disk
|
is honored only by initial RAM disk
|
||||||
(initrd) while
|
(initrd) while
|
||||||
@ -165,6 +181,44 @@
|
|||||||
the initrd.</para>
|
the initrd.</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>luks.options=</varname></term>
|
||||||
|
<term><varname>rd.luks.options=</varname></term>
|
||||||
|
|
||||||
|
<listitem><para>Takes a LUKS super block UUID followed by an
|
||||||
|
<literal>=</literal> and a string of options separated by
|
||||||
|
commas as argument. This will override the options for the
|
||||||
|
given UUID.</para>
|
||||||
|
<para>If only a list of options, without an UUID, is
|
||||||
|
specified, they apply to any UUIDs not specified elsewhere,
|
||||||
|
and without an entry in
|
||||||
|
<filename>/etc/crypttab</filename>.</para>
|
||||||
|
|
||||||
|
<para>This parameter is the analogue of the fourth <citerefentry><refentrytitle>crypttab</refentrytitle>
|
||||||
|
<manvolnum>5</manvolnum></citerefentry> field <replaceable>options</replaceable>.</para>
|
||||||
|
|
||||||
|
<para>It is possible to specify an external device which
|
||||||
|
should be mounted before we attempt to unlock the LUKS device.
|
||||||
|
systemd-cryptsetup will assemble LUKS device by combining
|
||||||
|
data device specified in <varname>luks.data</varname> with
|
||||||
|
detached LUKS header found in <literal>header=</literal>
|
||||||
|
argument. For example,
|
||||||
|
<varname>rd.luks.uuid=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40
|
||||||
|
<varname>rd.luks.options=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40=header=/luks.hdr:LABEL=hdrdev
|
||||||
|
<varname>rd.luks.data=</varname>b40f1abf-2a53-400a-889a-2eccc27eaa40=/dev/sdx.
|
||||||
|
Hence, in this case, we will attempt to mount file system
|
||||||
|
residing on the block device with label <literal>hdrdev</literal>, and look
|
||||||
|
for <literal>luks.hdr</literal> on that file system. Said header will be used
|
||||||
|
to unlock (decrypt) encrypted data stored on /dev/sdx.
|
||||||
|
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.options=</varname> is honored only by initial
|
||||||
|
RAM disk (initrd) while <varname>luks.options=</varname> is
|
||||||
|
honored by both the main system and the initrd.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ typedef struct crypto_device {
|
|||||||
char *uuid;
|
char *uuid;
|
||||||
char *keyfile;
|
char *keyfile;
|
||||||
char *keydev;
|
char *keydev;
|
||||||
|
char *headerdev;
|
||||||
|
char *datadev;
|
||||||
char *name;
|
char *name;
|
||||||
char *options;
|
char *options;
|
||||||
bool create;
|
bool create;
|
||||||
@ -277,13 +279,14 @@ static int create_disk(
|
|||||||
const char *device,
|
const char *device,
|
||||||
const char *password,
|
const char *password,
|
||||||
const char *keydev,
|
const char *keydev,
|
||||||
|
const char *headerdev,
|
||||||
const char *options,
|
const char *options,
|
||||||
const char *source) {
|
const char *source) {
|
||||||
|
|
||||||
_cleanup_free_ char *n = NULL, *d = NULL, *u = NULL, *e = NULL,
|
_cleanup_free_ char *n = NULL, *d = NULL, *u = NULL, *e = NULL,
|
||||||
*keydev_mount = NULL, *keyfile_timeout_value = NULL,
|
*keydev_mount = NULL, *keyfile_timeout_value = NULL,
|
||||||
*filtered = NULL, *u_escaped = NULL, *name_escaped = NULL, *header_path = NULL, *password_buffer = NULL,
|
*filtered = NULL, *u_escaped = NULL, *name_escaped = NULL, *header_path = NULL, *password_buffer = NULL,
|
||||||
*tmp_fstype = NULL;
|
*tmp_fstype = NULL, *filtered_header = NULL, *headerdev_mount = NULL;
|
||||||
_cleanup_fclose_ FILE *f = NULL;
|
_cleanup_fclose_ FILE *f = NULL;
|
||||||
const char *dmname;
|
const char *dmname;
|
||||||
bool noauto, nofail, swap, netdev, attach_in_initrd;
|
bool noauto, nofail, swap, netdev, attach_in_initrd;
|
||||||
@ -302,7 +305,12 @@ static int create_disk(
|
|||||||
if (keyfile_can_timeout < 0)
|
if (keyfile_can_timeout < 0)
|
||||||
return log_error_errno(keyfile_can_timeout, "Failed to parse keyfile-timeout= option value: %m");
|
return log_error_errno(keyfile_can_timeout, "Failed to parse keyfile-timeout= option value: %m");
|
||||||
|
|
||||||
detached_header = fstab_filter_options(options, "header\0", NULL, &header_path, NULL);
|
detached_header = fstab_filter_options(
|
||||||
|
options,
|
||||||
|
"header\0",
|
||||||
|
NULL,
|
||||||
|
&header_path,
|
||||||
|
headerdev ? &filtered_header : NULL);
|
||||||
if (detached_header < 0)
|
if (detached_header < 0)
|
||||||
return log_error_errno(detached_header, "Failed to parse header= option value: %m");
|
return log_error_errno(detached_header, "Failed to parse header= option value: %m");
|
||||||
|
|
||||||
@ -399,6 +407,55 @@ static int create_disk(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (headerdev) {
|
||||||
|
_cleanup_free_ char *unit = NULL, *umount_unit = NULL, *p = NULL;
|
||||||
|
|
||||||
|
r = generate_device_mount(
|
||||||
|
name,
|
||||||
|
headerdev,
|
||||||
|
"headerdev",
|
||||||
|
NULL,
|
||||||
|
/* canfail= */ false, /* header is always necessary */
|
||||||
|
/* readonly= */ false, /* LUKS2 recovery requires rw header access */
|
||||||
|
&unit,
|
||||||
|
&headerdev_mount);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to generate header device mount unit: %m");
|
||||||
|
|
||||||
|
r = generate_device_umount(name, headerdev_mount, "headerdev", &umount_unit);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to generate header device umount unit: %m");
|
||||||
|
|
||||||
|
p = path_join(headerdev_mount, header_path);
|
||||||
|
if (!p)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
free_and_replace(header_path, p);
|
||||||
|
|
||||||
|
if (isempty(filtered_header))
|
||||||
|
p = strjoin("header=", header_path);
|
||||||
|
else
|
||||||
|
p = strjoin(filtered_header, ",header=", header_path);
|
||||||
|
|
||||||
|
if (!p)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
free_and_replace(filtered_header, p);
|
||||||
|
options = filtered_header;
|
||||||
|
|
||||||
|
fprintf(f, "After=%s\n"
|
||||||
|
"Requires=%s\n", unit, unit);
|
||||||
|
|
||||||
|
if (umount_unit) {
|
||||||
|
fprintf(f,
|
||||||
|
"Wants=%s\n"
|
||||||
|
"Before=%s\n",
|
||||||
|
umount_unit,
|
||||||
|
umount_unit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!nofail)
|
if (!nofail)
|
||||||
fprintf(f,
|
fprintf(f,
|
||||||
"Before=%s\n",
|
"Before=%s\n",
|
||||||
@ -411,7 +468,7 @@ static int create_disk(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a header option was specified */
|
/* Check if a header option was specified */
|
||||||
if (detached_header > 0) {
|
if (detached_header > 0 && !headerdev) {
|
||||||
r = print_dependencies(f, header_path);
|
r = print_dependencies(f, header_path);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
@ -537,6 +594,41 @@ static bool warn_uuid_invalid(const char *uuid, const char *key) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int filter_header_device(const char *options,
|
||||||
|
char **ret_headerdev,
|
||||||
|
char **ret_filtered_headerdev_options) {
|
||||||
|
int r;
|
||||||
|
_cleanup_free_ char *headerfile = NULL, *headerdev = NULL, *headerspec = NULL,
|
||||||
|
*filtered_headerdev = NULL, *filtered_headerspec = NULL;
|
||||||
|
|
||||||
|
assert(ret_headerdev);
|
||||||
|
assert(ret_filtered_headerdev_options);
|
||||||
|
|
||||||
|
r = fstab_filter_options(options, "header\0", NULL, &headerspec, &filtered_headerspec);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse header= option value: %m");
|
||||||
|
|
||||||
|
if (r > 0) {
|
||||||
|
r = split_locationspec(headerspec, &headerfile, &headerdev);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (isempty(filtered_headerspec))
|
||||||
|
filtered_headerdev = strjoin("header=", headerfile);
|
||||||
|
else
|
||||||
|
filtered_headerdev = strjoin(filtered_headerspec, ",header=", headerfile);
|
||||||
|
|
||||||
|
if (!filtered_headerdev)
|
||||||
|
return log_oom();
|
||||||
|
} else
|
||||||
|
filtered_headerdev = TAKE_PTR(filtered_headerspec);
|
||||||
|
|
||||||
|
*ret_filtered_headerdev_options = TAKE_PTR(filtered_headerdev);
|
||||||
|
*ret_headerdev = TAKE_PTR(headerdev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
|
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
|
||||||
_cleanup_free_ char *uuid = NULL, *uuid_value = NULL;
|
_cleanup_free_ char *uuid = NULL, *uuid_value = NULL;
|
||||||
crypto_device *d;
|
crypto_device *d;
|
||||||
@ -570,20 +662,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
|||||||
d->create = arg_allow_list = true;
|
d->create = arg_allow_list = true;
|
||||||
|
|
||||||
} else if (streq(key, "luks.options")) {
|
} else if (streq(key, "luks.options")) {
|
||||||
|
_cleanup_free_ char *headerdev = NULL, *filtered_headerdev_options = NULL;
|
||||||
|
|
||||||
if (proc_cmdline_value_missing(key, value))
|
if (proc_cmdline_value_missing(key, value))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
|
r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
|
||||||
if (r == 2) {
|
if (r != 2)
|
||||||
d = get_crypto_device(uuid);
|
return free_and_strdup(&arg_default_options, value) < 0 ? log_oom() : 0;
|
||||||
if (!d)
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
free_and_replace(d->options, uuid_value);
|
if (warn_uuid_invalid(uuid, key))
|
||||||
} else if (free_and_strdup(&arg_default_options, value) < 0)
|
return 0;
|
||||||
|
|
||||||
|
d = get_crypto_device(uuid);
|
||||||
|
if (!d)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
|
r = filter_header_device(uuid_value, &headerdev, &filtered_headerdev_options);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
free_and_replace(d->options, filtered_headerdev_options);
|
||||||
|
free_and_replace(d->headerdev, headerdev);
|
||||||
} else if (streq(key, "luks.key")) {
|
} else if (streq(key, "luks.key")) {
|
||||||
size_t n;
|
size_t n;
|
||||||
_cleanup_free_ char *keyfile = NULL, *keydev = NULL;
|
_cleanup_free_ char *keyfile = NULL, *keydev = NULL;
|
||||||
@ -617,7 +717,35 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
|||||||
|
|
||||||
free_and_replace(d->keyfile, keyfile);
|
free_and_replace(d->keyfile, keyfile);
|
||||||
free_and_replace(d->keydev, keydev);
|
free_and_replace(d->keydev, keydev);
|
||||||
|
} else if (streq(key, "luks.data")) {
|
||||||
|
size_t n;
|
||||||
|
_cleanup_free_ char *datadev = NULL;
|
||||||
|
|
||||||
|
if (proc_cmdline_value_missing(key, value))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
n = strspn(value, ALPHANUMERICAL "-");
|
||||||
|
if (value[n] != '=') {
|
||||||
|
log_warning("Failed to parse luks.data= kernel command line switch. UUID is invalid, ignoring.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = strndup(value, n);
|
||||||
|
if (!uuid)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (warn_uuid_invalid(uuid, key))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
d = get_crypto_device(uuid);
|
||||||
|
if (!d)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
datadev = fstab_node_to_udev_node(value + n + 1);
|
||||||
|
if (!datadev)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
free_and_replace(d->datadev, datadev);
|
||||||
} else if (streq(key, "luks.name")) {
|
} else if (streq(key, "luks.name")) {
|
||||||
|
|
||||||
if (proc_cmdline_value_missing(key, value))
|
if (proc_cmdline_value_missing(key, value))
|
||||||
@ -701,7 +829,7 @@ static int add_crypttab_devices(void) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = create_disk(name, device, keyfile, keydev, (d && d->options) ? d->options : options, arg_crypttab);
|
r = create_disk(name, device, keyfile, keydev, d ? d->headerdev : NULL, (d && d->options) ? d->options : options, arg_crypttab);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
@ -733,9 +861,10 @@ static int add_proc_cmdline_devices(void) {
|
|||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = create_disk(d->name,
|
r = create_disk(d->name,
|
||||||
device,
|
d->datadev ?: device,
|
||||||
d->keyfile ?: arg_default_keyfile,
|
d->keyfile ?: arg_default_keyfile,
|
||||||
d->keydev,
|
d->keydev,
|
||||||
|
d->headerdev,
|
||||||
d->options ?: arg_default_options,
|
d->options ?: arg_default_options,
|
||||||
"/proc/cmdline");
|
"/proc/cmdline");
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user