summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/ENVIRONMENT.md4
-rw-r--r--man/integritytab.xml161
-rw-r--r--man/rules/meson.build6
-rw-r--r--man/systemd-integritysetup-generator.xml48
-rw-r--r--man/systemd-integritysetup@.service.xml95
-rw-r--r--man/systemd.special.xml2
-rw-r--r--meson.build20
-rw-r--r--src/integritysetup/integrity-util.c66
-rw-r--r--src/integritysetup/integrity-util.h19
-rw-r--r--src/integritysetup/integritysetup-generator.c181
-rw-r--r--src/integritysetup/integritysetup.c197
-rw-r--r--units/integritysetup-pre.target14
-rw-r--r--units/integritysetup.target12
-rw-r--r--units/meson.build3
14 files changed, 828 insertions, 0 deletions
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 175bb8a819..aba9ede259 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -50,6 +50,10 @@ All tools:
useful for debugging. Currently only supported by
`systemd-cryptsetup-generator`.
+* `$SYSTEMD_INTEGRITYTAB` — if set, use this path instead of
+ `/etc/integritytab`. Only useful for debugging. Currently only supported by
+ `systemd-integritysetup-generator`.
+
* `$SYSTEMD_VERITYTAB` — if set, use this path instead of
`/etc/veritytab`. Only useful for debugging. Currently only supported by
`systemd-veritysetup-generator`.
diff --git a/man/integritytab.xml b/man/integritytab.xml
new file mode 100644
index 0000000000..c2ad2573a0
--- /dev/null
+++ b/man/integritytab.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!--
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+-->
+<refentry id="integritytab" conditional='HAVE_LIBCRYPTSETUP' xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>integritytab</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>integritytab</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>integritytab</refname>
+ <refpurpose>Configuration for integrity block devices</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/etc/integritytab</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>The <filename>/etc/integritytab</filename> file describes
+ integrity protected block devices that are set up during
+ system boot.</para>
+
+ <para>Empty lines and lines starting with the <literal>#</literal>
+ character are ignored. Each of the remaining lines describes one
+ verity integrity protected block device. Fields are delimited by
+ white space.</para>
+
+ <para>Each line is in the form<programlisting><replaceable>volume-name</replaceable> <replaceable>block-device</replaceable>
+ <replaceable>[keyfile|-]</replaceable> <replaceable>[options|-]</replaceable></programlisting>
+ The first two fields are mandatory, the remaining two are optional and only required if user specified non-default options during integrity format.</para>
+
+ <para>The first field contains the name of the resulting integrity volume; its block device is set up
+ below <filename>/dev/mapper/</filename>.</para>
+
+ <para>The second field contains a path to the underlying block device, or a specification of a block device via
+ <literal>UUID=</literal> followed by the UUID,
+ <literal>PARTUUID=</literal> followed by the partition UUID,
+ <literal>LABEL=</literal> followed by the label,
+ <literal>PARTLABEL=</literal> followed by the partition label,
+ </para>
+
+ <para>The third field if present contains an absolute filename path to a key file or a <literal>-</literal>
+ to specify none. When the filename is present, the "integrity-algorithm" defaults to <literal>hmac-sha256</literal>
+ with the key length derived from the number of bytes in the key file. At this time the only supported integrity algorithm
+ when using key file is hmac-sha256. The maximum size of the key file is 4096 bytes.
+ </para>
+
+ <para>The fourth field, if present, is a comma-delimited list of options or a <literal>-</literal> to specify none. The following options are
+ recognized:</para>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>allow-discards</option></term>
+
+ <listitem><para>
+ Allow the use of discard (TRIM) requests for the device.
+ This option is available since the Linux kernel version 5.7.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>journal-watermark=[0..100]%</option></term>
+
+ <listitem><para>
+ Journal watermark in percent. When the journal percentage exceeds this watermark, the journal flush will be started. Setting a value of
+ "0%" uses default value.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>journal-commit-time=[0..N]</option></term>
+
+ <listitem><para>
+ Commit time in milliseconds. When this time passes (and no explicit flush operation was issued), the journal is written. Setting a value of
+ zero uses default value.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>data-device=/dev/disk/by-...</option></term>
+
+ <listitem><para>
+ Specify a separate block device that contains existing data. The second field specified in the
+ integritytab for block device then will contain calculated integrity tags and journal for data-device,
+ but not the end user data.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>integrity-algorithm=[crc32c|crc32|sha1|sha256|hmac-sha256]</option></term>
+
+ <listitem><para>
+ The algorithm used for integrity checking. The default is crc32c. Must match option used during format.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>At early boot and when the system manager configuration is
+ reloaded, this file is translated into native systemd units by
+ <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <example>
+ <title>/etc/integritytab</title>
+ <para>Set up two integrity protected block devices. </para>
+
+ <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 - journal-commit-time=10,allow-discards,journal-watermark=55%
+data PARTUUID=5d4b1808-be76-774d-88af-03c4c3a41761 - allow-discards
+</programlisting>
+ </example>
+
+ <example>
+ <title>/etc/integritytab</title>
+ <para>Set up 1 integrity protected block device using defaults </para>
+
+ <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8</programlisting>
+ </example>
+
+ <example>
+ <title>/etc/integritytab</title>
+ <para>Set up 1 integrity device using existing data block device which contains user data </para>
+
+ <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 - data-device=/dev/disk/by-uuid/9276d9c0-d4e3-4297-b4ff-3307cd0d092f</programlisting>
+ </example>
+
+ <example>
+ <title>/etc/integritytab</title>
+ <para>Set up 1 integrity device using a HMAC key file using defaults </para>
+
+ <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 /etc/hmac.key</programlisting>
+ </example>
+
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry project='die-net'><refentrytitle>integritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/rules/meson.build b/man/rules/meson.build
index f9c69f1846..ad7cc41c98 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -24,6 +24,7 @@ manpages = [
['hostname', '5', [], ''],
['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
['hwdb', '7', [], 'ENABLE_HWDB'],
+ ['integritytab', '5', [], 'HAVE_LIBCRYPTSETUP'],
['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'],
['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'],
['journalctl', '1', [], ''],
@@ -882,6 +883,11 @@ manpages = [
'8',
['systemd-initctl', 'systemd-initctl.socket'],
'HAVE_SYSV_COMPAT'],
+ ['systemd-integritysetup-generator', '8', [], 'HAVE_LIBCRYPTSETUP'],
+ ['systemd-integritysetup@.service',
+ '8',
+ ['systemd-integritysetup'],
+ 'HAVE_LIBCRYPTSETUP'],
['systemd-journal-gatewayd.service',
'8',
['systemd-journal-gatewayd', 'systemd-journal-gatewayd.socket'],
diff --git a/man/systemd-integritysetup-generator.xml b/man/systemd-integritysetup-generator.xml
new file mode 100644
index 0000000000..23eab015f6
--- /dev/null
+++ b/man/systemd-integritysetup-generator.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+<refentry id="systemd-integritysetup-generator" conditional='HAVE_LIBCRYPTSETUP'>
+
+ <refentryinfo>
+ <title>systemd-integritysetup-generator</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-integritysetup-generator</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-integritysetup-generator</refname>
+ <refpurpose>Unit generator for integrity protected block devices</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/usr/lib/systemd/system-generators/systemd-integritysetup-generator</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><filename>systemd-integritysetup-generator</filename> is a generator that translates <filename>/etc/integritytab</filename> entries into
+ native systemd units early at boot. This will create
+ <citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ units as necessary.</para>
+
+ <para><command>systemd-integritysetup-generator</command> implements
+ <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry project='die-net'><refentrytitle>integritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd-integritysetup@.service.xml b/man/systemd-integritysetup@.service.xml
new file mode 100644
index 0000000000..24336c262d
--- /dev/null
+++ b/man/systemd-integritysetup@.service.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+<refentry id="systemd-integritysetup@.service" conditional='HAVE_LIBCRYPTSETUP'>
+
+ <refentryinfo>
+ <title>systemd-integritysetup@.service</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-integritysetup@.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-integritysetup@.service</refname>
+ <refname>systemd-integritysetup</refname>
+ <refpurpose>Disk integrity protection logic</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-integritysetup@.service</filename></para>
+ <para><filename>/usr/lib/systemd/systemd-integritysetup</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><filename>systemd-integritysetup@.service</filename> is a service responsible for setting up integrity
+ protected block devices. It should be instantiated for each device that requires integrity
+ protection.</para>
+
+ <para>At early boot and when the system manager configuration is reloaded, entries from /etc/integritytab are converted into
+ <filename>systemd-integritysetup@.service</filename> units by
+ <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+
+ <para><filename>systemd-integritysetup@.service</filename> calls <command>systemd-integritysetup</command>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Commands</title>
+
+ <para>The following commands are understood by <command>systemd-integritysetup</command>:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>attach</option>
+ <replaceable>volume</replaceable>
+ <replaceable>device</replaceable>
+ [<replaceable>key-file|-</replaceable>]
+ [<replaceable>option(s)|-</replaceable>]
+ </term>
+
+ <listitem><para>Create a block device <replaceable>volume</replaceable> using
+ <replaceable>device</replaceable>. See integritytab man page and
+ <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-integrity.html">
+ Kernel dm-integrity</ulink> documentation for details.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>detach</option>
+ <replaceable>volume</replaceable>
+ </term>
+
+ <listitem><para>Detach (destroy) the block device
+ <replaceable>volume</replaceable>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>help</option>
+ </term>
+
+ <listitem><para>Print short information about command syntax.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>integritytab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry project='die-net'><refentrytitle>integritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd.special.xml b/man/systemd.special.xml
index 8755b523ae..9eb0b4a2c2 100644
--- a/man/systemd.special.xml
+++ b/man/systemd.special.xml
@@ -48,6 +48,8 @@
<filename>initrd-root-device.target</filename>,
<filename>initrd-root-fs.target</filename>,
<filename>initrd-usr-fs.target</filename>,
+ <filename>integritysetup-pre.target</filename>,
+ <filename>integritysetup.target</filename>,
<filename>kbrequest.target</filename>,
<filename>kexec.target</filename>,
<filename>local-fs-pre.target</filename>,
diff --git a/meson.build b/meson.build
index 6c5d4bb34c..df53c6156d 100644
--- a/meson.build
+++ b/meson.build
@@ -252,6 +252,7 @@ conf.set_quoted('SYSTEMD_GROWFS_PATH', rootlibexecdir / '
conf.set_quoted('SYSTEMD_HOMEWORK_PATH', rootlibexecdir / 'systemd-homework')
conf.set_quoted('SYSTEMD_IMPORT_FS_PATH', rootlibexecdir / 'systemd-import-fs')
conf.set_quoted('SYSTEMD_IMPORT_PATH', rootlibexecdir / 'systemd-import')
+conf.set_quoted('SYSTEMD_INTEGRITYSETUP_PATH', rootlibexecdir / 'systemd-integritysetup')
conf.set_quoted('SYSTEMD_KBD_MODEL_MAP', pkgdatadir / 'kbd-model-map')
conf.set_quoted('SYSTEMD_LANGUAGE_FALLBACK_MAP', pkgdatadir / 'language-fallback-map')
conf.set_quoted('SYSTEMD_MAKEFS_PATH', rootlibexecdir / 'systemd-makefs')
@@ -2535,6 +2536,25 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
libp11kit],
install_rpath : rootlibexecdir,
install : true)
+
+ executable(
+ 'systemd-integritysetup',
+ ['src/integritysetup/integritysetup.c', 'src/integritysetup/integrity-util.c'],
+ include_directories : includes,
+ link_with : [libshared],
+ dependencies : [libcryptsetup],
+ install_rpath : rootlibexecdir,
+ install : true,
+ install_dir : rootlibexecdir)
+
+ executable(
+ 'systemd-integritysetup-generator',
+ ['src/integritysetup/integritysetup-generator.c', 'src/integritysetup/integrity-util.c'],
+ include_directories : includes,
+ link_with : [libshared],
+ install_rpath : rootlibexecdir,
+ install : true,
+ install_dir : systemgeneratordir)
endif
if conf.get('HAVE_SYSV_COMPAT') == 1
diff --git a/src/integritysetup/integrity-util.c b/src/integritysetup/integrity-util.c
new file mode 100644
index 0000000000..5970a136b8
--- /dev/null
+++ b/src/integritysetup/integrity-util.c
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "integrity-util.h"
+
+#include "extract-word.h"
+#include "fileio.h"
+#include "path-util.h"
+#include "percent-util.h"
+
+
+static int supported_integrity_algorithm(char *user_supplied) {
+ if (!STR_IN_SET(user_supplied, "crc32", "crc32c", "sha1", "sha256", "hmac-sha256"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported integrity algorithm (%s)", user_supplied);
+ return 0;
+}
+
+int parse_integrity_options(
+ const char *options,
+ uint32_t *ret_activate_flags,
+ int *ret_percent,
+ usec_t *ret_commit_time,
+ char **ret_data_device,
+ char **ret_integrity_alg) {
+ int r;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ char *val;
+
+ r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse options: %m");
+ if (r == 0)
+ break;
+ else if (streq(word, "allow-discards")) {
+ if (ret_activate_flags)
+ *ret_activate_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
+ } else if ((val = startswith(word, "journal-watermark="))) {
+ r = parse_percent(val);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse journal-watermark value or value out of range (%s)", val);
+ if (ret_percent)
+ *ret_percent = r;
+ } else if ((val = startswith(word, "journal-commit-time="))) {
+ usec_t tmp_commit_time;
+ r = parse_sec(val, &tmp_commit_time);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse journal-commit-time value (%s)", val);
+ if (ret_commit_time)
+ *ret_commit_time = tmp_commit_time;
+ } else if ((val = startswith(word, "data-device="))) {
+ r = free_and_strdup(ret_data_device, val);
+ if (r < 0)
+ return log_oom();
+ } else if ((val = startswith(word, "integrity-algorithm="))) {
+ r = free_and_strdup(ret_integrity_alg, val);
+ if (r < 0)
+ return log_oom();
+ r = supported_integrity_algorithm(*ret_integrity_alg);
+ if (r < 0)
+ return r;
+ } else
+ log_warning("Encountered unknown option '%s', ignoring.", word);
+ }
+
+ return r;
+}
diff --git a/src/integritysetup/integrity-util.h b/src/integritysetup/integrity-util.h
new file mode 100644
index 0000000000..b27975c7db
--- /dev/null
+++ b/src/integritysetup/integrity-util.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdint.h>
+
+#include "cryptsetup-util.h"
+#include "time-util.h"
+
+
+int parse_integrity_options(
+ const char *options,
+ uint32_t *ret_activate_flags,
+ int *ret_percent,
+ usec_t *ret_commit_time,
+ char **ret_data_device,
+ char **ret_integrity_alg);
+
+#define DM_HMAC_256 "hmac(sha256)"
+#define DM_MAX_KEY_SIZE 4096 /* Maximum size of key allowed for dm-integrity */
diff --git a/src/integritysetup/integritysetup-generator.c b/src/integritysetup/integritysetup-generator.c
new file mode 100644
index 0000000000..15f508902d
--- /dev/null
+++ b/src/integritysetup/integritysetup-generator.c
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fstab-util.h"
+#include "generator.h"
+#include "hexdecoct.h"
+#include "id128-util.h"
+#include "integrity-util.h"
+#include "main-func.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
+#include "specifier.h"
+#include "string-util.h"
+#include "unit-name.h"
+
+static const char *arg_dest = NULL;
+static const char *arg_integritytab = NULL;
+static char *arg_options = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_options, freep);
+
+static int create_disk(
+ const char *name,
+ const char *device,
+ const char *key_file,
+ const char *options) {
+
+ _cleanup_free_ char *n = NULL, *dd = NULL, *e = NULL, *name_escaped = NULL, *key_file_escaped = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+ char *dmname = NULL;
+
+ assert(name);
+ assert(device);
+
+ name_escaped = specifier_escape(name);
+ if (!name_escaped)
+ return log_oom();
+
+ e = unit_name_escape(name);
+ if (!e)
+ return log_oom();
+
+ r = unit_name_build("systemd-integritysetup", e, ".service", &n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ r = unit_name_from_path(device, ".device", &dd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ r = generator_open_unit_file(arg_dest, NULL, n, &f);
+ if (r < 0)
+ return r;
+
+ if (key_file) {
+ if (!path_is_absolute(key_file))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "key file not absolute file path %s", key_file);
+
+ key_file_escaped = specifier_escape(key_file);
+ if (!key_file_escaped)
+ return log_oom();
+ }
+
+ if (options) {
+ r = parse_integrity_options(options, NULL, NULL, NULL, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ fprintf(f,
+ "[Unit]\n"
+ "Description=Integrity Setup for %%I\n"
+ "Documentation=man:integritytab(5) man:systemd-integritysetup-generator(8) man:systemd-integritysetup@.service(8)\n"
+ "SourcePath=%s\n"
+ "DefaultDependencies=no\n"
+ "IgnoreOnIsolate=true\n"
+ "After=integritysetup-pre.target systemd-udevd-kernel.socket\n"
+ "Before=blockdev@dev-mapper-%%i.target\n"
+ "Wants=blockdev@dev-mapper-%%i.target\n"
+ "Conflicts=umount.target\n"
+ "Before=integritysetup.target\n"
+ "BindsTo=%s\n"
+ "After=%s\n"
+ "Before=umount.target\n",
+ arg_integritytab,
+ dd, dd);
+
+ fprintf(f,
+ "\n"
+ "[Service]\n"
+ "Type=oneshot\n"
+ "RemainAfterExit=yes\n"
+ "TimeoutSec=0\n"
+ "ExecStart=" ROOTLIBEXECDIR "/systemd-integritysetup attach '%s' '%s' '%s' '%s'\n"
+ "ExecStop=" ROOTLIBEXECDIR "/systemd-integritysetup detach '%s'\n",
+ name_escaped, device, empty_to_dash(key_file_escaped), empty_to_dash(options),
+ name_escaped);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write unit file %s: %m", n);
+
+ r = generator_add_symlink(arg_dest, "integritysetup.target", "requires", n);
+ if (r < 0)
+ return r;
+
+ dmname = strjoina("dev-mapper-", e, ".device");
+ return generator_add_symlink(arg_dest, dmname, "requires", n);
+}
+
+static int add_integritytab_devices(void) {
+ _cleanup_fclose_ FILE *f = NULL;
+ unsigned integritytab_line = 0;
+ int r;
+
+ r = fopen_unlocked(arg_integritytab, "re", &f);
+ if (r < 0) {
+ if (errno != ENOENT)
+ log_error_errno(errno, "Failed to open %s: %m", arg_integritytab);
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL, *name = NULL, *device_id = NULL, *device_path = NULL, *key_file = NULL, *options = NULL;
+ char *l;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", arg_integritytab);
+ if (r == 0)
+ break;
+
+ integritytab_line++;
+
+ l = strstrip(line);
+ if (!l)
+ continue;
+
+ if (IN_SET(l[0], 0, '#'))
+ continue;
+
+ /* The key file and the options are optional */
+ r = sscanf(l, "%ms %ms %ms %ms", &name, &device_id, &key_file, &options);
+ if (!IN_SET(r, 2, 3, 4)) {
+ log_error("Failed to parse %s:%u, ignoring.", l, integritytab_line);
+ continue;
+ }
+
+ device_path = fstab_node_to_udev_node(device_id);
+ if (!device_path) {
+ log_error("Failed to find device %s:%u, ignoring.", device_id, integritytab_line);
+ continue;
+ }
+
+ r = create_disk(name, device_path, empty_or_dash_to_null(key_file), empty_or_dash_to_null(options));
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int run(const char *dest, const char *dest_early, const char *dest_late) {
+ assert_se(arg_dest = dest);
+
+ arg_integritytab = getenv("SYSTEMD_INTEGRITYTAB") ?: "/etc/integritytab";
+
+ return add_integritytab_devices();
+}
+
+DEFINE_MAIN_GENERATOR_FUNCTION(run);
diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c
new file mode 100644
index 0000000000..d8cfd12e95
--- /dev/null
+++ b/src/integritysetup/integritysetup.c
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include "alloc-util.h"
+#include "cryptsetup-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "integrity-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "memory-util.h"
+#include "path-util.h"
+#include "parse-util.h"
+#include "pretty-print.h"
+#include "string-util.h"
+#include "terminal-util.h"
+
+static uint32_t arg_activate_flags;
+static int arg_percent;
+static usec_t arg_commit_time;
+static char *arg_existing_data_device;
+static char *arg_integrity_algorithm;
+
+STATIC_DESTRUCTOR_REGISTER(arg_existing_data_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_integrity_algorithm, freep);
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-integritysetup@.service", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s attach VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]\n"
+ "%s detach VOLUME\n\n"
+ "Attach or detach an integrity protected block device.\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ program_invocation_short_name,
+ link);
+
+ return 0;
+}
+
+static int load_key_file(
+ const char *key_file,
+ void **ret_key_file_contents,
+ size_t *ret_key_file_size) {
+ int r;
+ _cleanup_(erase_and_freep) char *tmp_key_file_contents = NULL;
+ size_t tmp_key_file_size;
+
+ if (!path_is_absolute(key_file))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "key file not absolute path: %s", key_file);
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file, UINT64_MAX, DM_MAX_KEY_SIZE,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER,
+ NULL,
+ &tmp_key_file_contents, &tmp_key_file_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to process key file: %m");
+
+ if (ret_key_file_contents && ret_key_file_size) {
+ *ret_key_file_contents = TAKE_PTR(tmp_key_file_contents);
+ *ret_key_file_size = tmp_key_file_size;
+ }
+
+ return 0;
+}
+
+static const char *integrity_algorithm_select(const void *key_file_buf) {
+ /* To keep a bit of sanity for end users, the subset of integrity
+ algorithms we support will match what is used in integritysetup */
+ if (arg_integrity_algorithm) {
+ if (streq("hmac-sha256", arg_integrity_algorithm))
+ return DM_HMAC_256;
+ return arg_integrity_algorithm;
+ } else if (key_file_buf)
+ return DM_HMAC_256;
+ return "crc32c";
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+ int r;
+ char *action, *volume;
+
+ if (argc <= 1 ||
+ strv_contains(strv_skip(argv, 1), "--help") ||
+ strv_contains(strv_skip(argv, 1), "-h") ||
+ streq(argv[1], "help"))
+ return help();
+
+ if (argc < 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments.");
+
+ action = argv[1];
+ volume = argv[2];
+
+ log_setup();
+
+ cryptsetup_enable_logging(NULL);
+
+ umask(0022);
+
+ if (streq(action, "attach")) {
+ /* attach name device optional_key_file optional_options */
+
+ crypt_status_info status;
+ _cleanup_(erase_and_freep) void *key_buf = NULL;
+ const char *device, *key_file, *options;
+ size_t key_buf_size = 0;
+
+ if (argc < 4)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least three arguments.");
+
+ if (argc > 6)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach has a maximum of five arguments.");
+
+ device = argv[3];
+ key_file = (argc > 4) ? empty_or_dash_to_null(argv[4]) : NULL;
+ options = (argc > 5) ? empty_or_dash_to_null(argv[5]) : NULL;
+
+ if (key_file) {
+ r = load_key_file(key_file, &key_buf, &key_buf_size);
+ if (r < 0)
+ return r;
+ }
+
+ if (options) {
+ r = parse_integrity_options(options, &arg_activate_flags, &arg_percent,
+ &arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm);
+ if (r < 0)
+ return r;
+ }
+
+ r = crypt_init(&cd, device);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open integrity device %s: %m", device);
+
+ cryptsetup_enable_logging(cd);
+
+ status = crypt_status(cd, volume);
+ if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) {
+ log_info("Volume %s already active.", volume);
+ return 0;
+ }
+
+ if (!isempty(arg_existing_data_device)) {
+ r = crypt_init_data_device(&cd, device, arg_existing_data_device);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add separate data device: %m");
+ }
+
+ r = crypt_load(cd,
+ CRYPT_INTEGRITY,
+ &(struct crypt_params_integrity) {
+ .journal_watermark = arg_percent,
+ .journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC),
+ .integrity = integrity_algorithm_select(key_buf),
+ });
+ if (r < 0)
+ return log_error_errno(r, "Failed to load integrity superblock: %m");
+
+ r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set up integrity device: %m");
+
+ } else if (streq(action, "detach")) {
+
+ if (argc > 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach has a maximum of two arguments.");
+
+ r = crypt_init_by_name(&cd, volume);
+ if (r == -ENODEV)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "crypt_init_by_name() failed: %m");
+
+ cryptsetup_enable_logging(cd);
+
+ r = crypt_deactivate(cd, volume);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deactivate: %m");
+
+ } else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", action);
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/units/integritysetup-pre.target b/units/integritysetup-pre.target
new file mode 100644
index 0000000000..da2aca9a28
--- /dev/null
+++ b/units/integritysetup-pre.target
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Local Integrity Protected Volumes (Pre)
+Documentation=man:systemd.special(7)
+RefuseManualStart=yes
+Before=integritysetup.target
diff --git a/units/integritysetup.target b/units/integritysetup.target
new file mode 100644
index 0000000000..371490f883
--- /dev/null
+++ b/units/integritysetup.target
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Local Integrity Protected Volumes
+Documentation=man:systemd.special(7)
diff --git a/units/meson.build b/units/meson.build
index 93c3bf608a..c106284e0f 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -13,6 +13,9 @@ units = [
['veritysetup-pre.target', 'HAVE_LIBCRYPTSETUP'],
['veritysetup.target', 'HAVE_LIBCRYPTSETUP',
'sysinit.target.wants/'],
+ ['integritysetup-pre.target', 'HAVE_LIBCRYPTSETUP'],
+ ['integritysetup.target', 'HAVE_LIBCRYPTSETUP',
+ 'sysinit.target.wants/'],
['dev-hugepages.mount', '',
'sysinit.target.wants/'],
['dev-mqueue.mount', '',