Files
slocker-lite/tests/integration/test_config_bwrap_chain.cpp
T
ceamac cdf9dcd210 Add global.with-veth/with-ipv6 config defaults for -n/--network creation
Replaces --no-ipv6/--no-veth (plain flags) with --with-ipv6/--with-veth,
each taking an explicit true/false value (e.g. --with-veth=false), parsed
via the same parse_bool_flag() the config file itself already uses (now
exported from config_file.h so cli_args.cpp can reuse it).

create_network_command() now resolves ipv6/veth as CLI flag -> config's own
global.with-ipv6/global.with-veth -> true, so a host that always wants the
tap+relay fallback (or no IPv6) can set it once in the config instead of
passing the flag on every network creation. -w/--write-config fills in both
new keys like the existing six unshare-* bools.
2026-09-05 08:36:12 +00:00

128 lines
5.2 KiB
C++

// Copyright (C) 2026 Viorel Munteanu
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// [integration] (no net, no root): chains config_file.h's read/write with
// bwrap.h's argv assembly -- write a config file, load it back, resolve a
// NamespaceConfig from it the same way commands.cpp's run_container() does,
// and confirm build_bwrap_args()'s resulting argv actually reflects it.
// Neither step mounts/runs anything or needs any privilege -- build_bwrap_args()
// is pure argv assembly, given a `root` that's just a string here, never
// actually accessed.
#include <algorithm>
#include <string>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include "bwrap.h"
#include "config_file.h"
#include "fixtures.h"
namespace {
bool contains(const std::vector<std::string>& argv, const std::string& flag) {
return std::find(argv.begin(), argv.end(), flag) != argv.end();
}
} // namespace
TEST_CASE("config file -> NamespaceConfig -> bwrap argv: disabled namespaces are never requested", "[integration]") {
ScratchXdgDirs scratch;
auto config_path = scratch.path() / "config.yaml";
AppConfig written;
written.unshare_net = false;
written.unshare_uts = false;
REQUIRE(write_config_file(config_path, written));
auto loaded = load_config_file(config_path);
REQUIRE(loaded.has_value());
CHECK(loaded->unshare_net == std::optional<bool>(false));
CHECK(loaded->unshare_uts == std::optional<bool>(false));
// Same resolution run_container() (commands.cpp) itself does: each
// unshare-* key defaults to enabled when unset.
NamespaceConfig namespace_config{
loaded->unshare_user.value_or(true), loaded->unshare_ipc.value_or(true),
loaded->unshare_pid.value_or(true), loaded->unshare_net.value_or(true),
loaded->unshare_uts.value_or(true), loaded->unshare_cgroup.value_or(true),
};
CHECK(namespace_config.net == false);
CHECK(namespace_config.uts == false);
CHECK(namespace_config.user == true);
auto argv = build_bwrap_args("/fake/root", {"/bin/sh"}, {}, std::nullopt, std::nullopt, namespace_config, false);
CHECK_FALSE(contains(argv, "--unshare-net"));
CHECK_FALSE(contains(argv, "--unshare-uts"));
CHECK(contains(argv, "--bind"));
CHECK(contains(argv, "/fake/root"));
CHECK(contains(argv, "/bin/sh"));
}
TEST_CASE("config file -> NamespaceConfig -> bwrap argv: default (unset) config matches real kernel support",
"[integration]") {
ScratchXdgDirs scratch;
auto config_path = scratch.path() / "config.yaml";
// Nothing set -- write_config_file()/load_config_file() round-trip an
// otherwise-empty AppConfig, so every unshare-* key comes back unset.
REQUIRE(write_config_file(config_path, AppConfig{}));
auto loaded = load_config_file(config_path);
REQUIRE(loaded.has_value());
CHECK_FALSE(loaded->unshare_net.has_value());
NamespaceConfig namespace_config{
loaded->unshare_user.value_or(true), loaded->unshare_ipc.value_or(true),
loaded->unshare_pid.value_or(true), loaded->unshare_net.value_or(true),
loaded->unshare_uts.value_or(true), loaded->unshare_cgroup.value_or(true),
};
auto argv = build_bwrap_args("/fake/root", {"true"}, {}, std::nullopt, std::nullopt, namespace_config, false);
// With every policy gate open, the only thing left restricting which
// --unshare-xxx flags actually appear is real kernel support -- so the
// resulting argv should exactly match detect_bwrap_unshare_args()'s
// own live probe of this host.
for (const auto& flag : detect_bwrap_unshare_args()) {
CHECK(contains(argv, flag));
}
}
TEST_CASE("config file -> AppConfig: global.with-veth/with-ipv6 round-trip", "[integration]") {
ScratchXdgDirs scratch;
auto config_path = scratch.path() / "config.yaml";
AppConfig written;
written.with_veth = false;
written.with_ipv6 = false;
REQUIRE(write_config_file(config_path, written));
auto loaded = load_config_file(config_path);
REQUIRE(loaded.has_value());
CHECK(loaded->with_veth == std::optional<bool>(false));
CHECK(loaded->with_ipv6 == std::optional<bool>(false));
// Same "unset means enabled" convention as the six unshare-* keys --
// create_network_command()'s own resolution (commands.cpp) is
// `args.network_with_*_flag.value_or(config.with_*.value_or(true))`.
auto loaded_empty = load_config_file(scratch.path() / "nonexistent.yaml");
REQUIRE(loaded_empty.has_value());
CHECK_FALSE(loaded_empty->with_veth.has_value());
CHECK_FALSE(loaded_empty->with_ipv6.has_value());
}