👷 gucc: add crypttab generation based on provided partitions

This commit is contained in:
Vladislav Nepogodin 2024-07-01 15:50:37 +04:00
parent f5f8886e00
commit 3facb2a41c
No known key found for this signature in database
GPG Key ID: B62C3D10C54D5DA9
7 changed files with 267 additions and 0 deletions

View File

@ -39,6 +39,7 @@ jobs:
./gucc/tests/test-initcpio
./gucc/tests/test-pacmanconf
./gucc/tests/test-fstab_gen
./gucc/tests/test-crypttab_gen
shell: bash
build-cmake_withoutdev:
name: Build with CMake (DEVENV OFF)
@ -84,6 +85,7 @@ jobs:
./gucc/tests/test-initcpio
./gucc/tests/test-pacmanconf
./gucc/tests/test-fstab_gen
./gucc/tests/test-crypttab_gen
shell: bash
build-meson_withoutdev:
name: Build with Meson (DEVENV OFF)

View File

@ -23,6 +23,7 @@ add_library(${PROJECT_NAME} SHARED
src/user.cpp include/gucc/user.hpp
src/locale.cpp include/gucc/locale.hpp
src/fstab.cpp include/gucc/fstab.hpp
src/crypttab.cpp include/gucc/crypttab.hpp
#src/chwd_profiles.cpp src/chwd_profiles.hpp
#src/disk.cpp src/disk.hpp
)

View File

@ -0,0 +1,20 @@
#ifndef CRYPTTAB_HPP
#define CRYPTTAB_HPP
#include "gucc/partition.hpp"
#include <string> // for string
#include <string_view> // for string_view
#include <vector> // for vector
namespace gucc::fs {
// Generate crypttab
auto generate_crypttab(const std::vector<Partition>& partitions, std::string_view root_mountpoint, std::string_view crypttab_opts) noexcept -> bool;
// Generate crypttab into string
auto generate_crypttab_content(const std::vector<Partition>& partitions, std::string_view crypttab_opts) noexcept -> std::string;
} // namespace gucc::fs
#endif // CRYPTTAB_HPP

View File

@ -13,6 +13,7 @@ gucc_lib = library('gucc',
'src/user.cpp',
'src/locale.cpp',
'src/fstab.cpp',
'src/crypttab.cpp',
],
include_directories : [include_directories('include')],
dependencies: deps

118
gucc/src/crypttab.cpp Normal file
View File

@ -0,0 +1,118 @@
#include "gucc/crypttab.hpp"
#include "gucc/partition.hpp"
#include <algorithm> // for any_of, sort, unique_copy
#include <filesystem>
#include <fstream>
#include <fmt/compile.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuseless-cast"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include <range/v3/algorithm/any_of.hpp>
#include <range/v3/algorithm/sort.hpp>
#include <range/v3/algorithm/unique_copy.hpp>
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
namespace fs = std::filesystem;
using namespace std::string_literals;
using namespace std::string_view_literals;
namespace {
// NOLINTNEXTLINE
static constexpr auto CRYPTTAB_HEADER = R"(# Configuration for encrypted block devices
# See crypttab(5) for details.
# NOTE: Do not list your root (/) partition here, it must be set up
# beforehand by the initramfs (/etc/mkinitcpio.conf).
# <name> <device> <password> <options>
)"sv;
} // namespace
namespace gucc::fs {
auto gen_crypttab_entry(const Partition& partition, std::string_view crypttab_opts, bool is_root_encrypted, bool is_boot_encrypted) noexcept -> std::optional<std::string> {
// skip if partition is not encypted
if (!partition.luks_mapper_name || !partition.luks_uuid) {
return std::nullopt;
}
// skip invalid usage
if (partition.luks_mapper_name->empty() || partition.luks_uuid->empty()) {
return std::nullopt;
}
auto crypt_password = "/crypto_keyfile.bin"s;
auto crypt_options = fmt::format(FMT_COMPILE(" {}"), crypttab_opts);
// 1. if root partition is not encrypted
// 2. if root partition is encrypted, but boot partition is not encrypted
if (!is_root_encrypted || (partition.mountpoint == "/"sv && !is_boot_encrypted)) {
crypt_password = "none"s;
crypt_options = ""s;
}
const auto& device_str = fmt::format(FMT_COMPILE("UUID={}"), *partition.luks_uuid);
return std::make_optional<std::string>(fmt::format(FMT_COMPILE("{:21} {:<45} {}{}\n"), *partition.luks_mapper_name, device_str, crypt_password, crypt_options));
}
auto generate_crypttab_content(const std::vector<Partition>& partitions, std::string_view crypttab_opts) noexcept -> std::string {
std::string crypttab_content{CRYPTTAB_HEADER};
// sort by mountpoint & device
auto partitions_sorted{partitions};
ranges::sort(partitions_sorted, {}, &Partition::mountpoint);
ranges::sort(partitions_sorted, {}, &Partition::device);
// filter duplicates
std::vector<Partition> partitions_filtered{};
ranges::unique_copy(
partitions_sorted, std::back_inserter(partitions_filtered),
{},
&Partition::device);
const bool is_root_encrypted = ranges::any_of(partitions, [](auto&& part) { return part.mountpoint == "/"sv && part.luks_mapper_name; });
const bool is_boot_encrypted = ranges::any_of(partitions, [](auto&& part) { return part.mountpoint == "/bool"sv && part.luks_mapper_name; });
for (auto&& partition : partitions_filtered) {
auto crypttab_entry = gen_crypttab_entry(partition, crypttab_opts, is_root_encrypted, is_boot_encrypted);
if (!crypttab_entry) {
continue;
}
crypttab_content += *crypttab_entry;
}
return crypttab_content;
}
auto generate_crypttab(const std::vector<Partition>& partitions, std::string_view root_mountpoint, std::string_view crypttab_opts) noexcept -> bool {
const auto& crypttab_filepath = fmt::format(FMT_COMPILE("{}/etc/crypttab"), root_mountpoint);
std::ofstream crypttab_file{crypttab_filepath, std::ios::out | std::ios::trunc};
if (!crypttab_file.is_open()) {
spdlog::error("Failed to open crypttab for writing {}", crypttab_filepath);
return false;
}
crypttab_file << fs::generate_crypttab_content(partitions, crypttab_opts);
return true;
}
} // namespace gucc::fs

View File

@ -21,3 +21,11 @@ executable(
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)
executable(
'test-crypttab_gen',
files('unit-crypttab_gen.cpp'),
dependencies: deps,
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)

View File

@ -0,0 +1,117 @@
#include "gucc/crypttab.hpp"
#include <cassert>
#include <string>
#include <string_view>
#include <vector>
#include <spdlog/sinks/callback_sink.h>
#include <spdlog/spdlog.h>
using namespace std::string_literals;
using namespace std::string_view_literals;
static constexpr auto CRYPTTAB_EMPTY_TEST = R"(# Configuration for encrypted block devices
# See crypttab(5) for details.
# NOTE: Do not list your root (/) partition here, it must be set up
# beforehand by the initramfs (/etc/mkinitcpio.conf).
# <name> <device> <password> <options>
)"sv;
static constexpr auto CRYPTTAB_UNENCR_BOOT_TEST = R"(# Configuration for encrypted block devices
# See crypttab(5) for details.
# NOTE: Do not list your root (/) partition here, it must be set up
# beforehand by the initramfs (/etc/mkinitcpio.conf).
# <name> <device> <password> <options>
luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f UUID=6bdb3301-8efb-4b84-b0b7-4caeef26fd6f none
)"sv;
int main() {
auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>([](const spdlog::details::log_msg& msg) {
// noop
});
spdlog::set_default_logger(std::make_shared<spdlog::logger>("default", callback_sink));
const auto& uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s;
const auto& btrfs_mountopts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s;
const auto& xfs_mountopts = "defaults,lazytime,noatime,attr2,inode64,logbsize=256k,noquota"s;
// btrfs with subvolumes
{
const std::vector<gucc::fs::Partition> partitions{
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/"s, .uuid_str = uuid_str, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = {}, .subvolume = "/@"s},
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/home"s, .uuid_str = uuid_str, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = {}, .subvolume = "/@home"s},
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/var/cache"s, .uuid_str = uuid_str, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = {}, .subvolume = "/@cache"s},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
const auto& crypttab_content = gucc::fs::generate_crypttab_content(partitions, "luks"sv);
assert(crypttab_content == CRYPTTAB_EMPTY_TEST);
}
// basic xfs
{
const std::vector<gucc::fs::Partition> partitions{
gucc::fs::Partition{.fstype = "xfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = xfs_mountopts},
gucc::fs::Partition{.fstype = "fat16"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
const auto& crypttab_content = gucc::fs::generate_crypttab_content(partitions, "luks"sv);
assert(crypttab_content == CRYPTTAB_EMPTY_TEST);
}
// luks xfs
{
const std::vector<gucc::fs::Partition> partitions{
gucc::fs::Partition{.fstype = "xfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = xfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s},
gucc::fs::Partition{.fstype = "linuxswap"s, .mountpoint = ""s, .uuid_str = ""s, .device = "/dev/nvme0n1p3"s, .mount_opts = "defaults,noatime"s},
gucc::fs::Partition{.fstype = "vfat"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
const auto& crypttab_content = gucc::fs::generate_crypttab_content(partitions, "luks"sv);
assert(crypttab_content == CRYPTTAB_UNENCR_BOOT_TEST);
}
// zfs
{
const std::vector<gucc::fs::Partition> partitions{
gucc::fs::Partition{.fstype = "zfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s},
gucc::fs::Partition{.fstype = "zfs"s, .mountpoint = "/home"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s},
gucc::fs::Partition{.fstype = "zfs"s, .mountpoint = "/var/cache"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s},
gucc::fs::Partition{.fstype = "vfat"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
const auto& crypttab_content = gucc::fs::generate_crypttab_content(partitions, "luks"sv);
assert(crypttab_content == CRYPTTAB_EMPTY_TEST);
}
// luks btrfs with subvolumes
{
const std::vector<gucc::fs::Partition> partitions{
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/"s, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .subvolume = "/@"s},
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/home"s, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .subvolume = "/@home"s},
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/var/cache"s, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .subvolume = "/@cache"s},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
const auto& crypttab_content = gucc::fs::generate_crypttab_content(partitions, "luks"sv);
assert(crypttab_content == CRYPTTAB_UNENCR_BOOT_TEST);
}
// luks btrfs with subvolumes {shuffled}
{
const std::vector<gucc::fs::Partition> partitions{
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/home"s, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .subvolume = "/@home"s},
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/"s, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .subvolume = "/@"s},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/var/cache"s, .device = "/dev/nvme0n1p1"s, .mount_opts = btrfs_mountopts, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .subvolume = "/@cache"s},
};
const auto& crypttab_content = gucc::fs::generate_crypttab_content(partitions, "luks"sv);
assert(crypttab_content == CRYPTTAB_UNENCR_BOOT_TEST);
}
/*
{'claimed': False, 'device': '', 'features': {}, 'fs': 'unknown', 'fsName': 'unknown', 'mountPoint': '', 'partattrs': 0, 'partlabel': '', 'parttype': '', 'partuuid': '', 'uuid': ''} None
{'claimed': True, 'device': '/dev/sda1', 'features': {}, 'fs': 'fat32', 'fsName': 'fat32', 'mountPoint': '/boot', 'partattrs': 0, 'partlabel': '', 'parttype': '', 'partuuid': '57F0553F-8C12-46DC-BDA7-868728368EEF', 'uuid': '86F8-2CD4'} None
{'claimed': True, 'device': '/dev/sda2', 'features': {}, 'fs': 'btrfs', 'fsName': 'luks2', 'luksMapperName': 'luks-6363636e-bc94-46fa-8ede-86cb57295161', 'luksPassphrase': '123456789', 'luksUuid': '6363636e-bc94-46fa-8ede-86cb57295161', 'mountPoint': '/', 'partattrs': 0, 'partlabel': 'root', 'parttype': '', 'partuuid': '2B6DA8D5-9A95-4917-B736-8F204E393488', 'uuid': '6363636e-bc94-46fa-8ede-86cb57295161'} {'name': 'luks-6363636e-bc94-46fa-8ede-86cb57295161', 'device': 'UUID=6363636e-bc94-46fa-8ede-86cb57295161', 'password': 'none', 'options': ''}
*/
}