1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-18 00:12:43 +01:00

pasta: wip

TODO: add original authors as commit authors
This commit is contained in:
Jörg Thalheim 2025-07-24 13:17:58 +02:00
parent 82315c3807
commit b2c35b45d2
16 changed files with 653 additions and 3 deletions

View file

@ -27,3 +27,10 @@ option(
value : false, value : false,
description : 'Build benchmarks (requires gbenchmark)', description : 'Build benchmarks (requires gbenchmark)',
) )
option(
'pasta-path',
type : 'string',
value : 'pasta',
description : 'Path to the location of pasta (provided by passt)',
)

View file

@ -89,6 +89,10 @@ Settings::Settings()
sandboxPaths = {{"/bin/sh", {.source = SANDBOX_SHELL}}}; sandboxPaths = {{"/bin/sh", {.source = SANDBOX_SHELL}}};
#endif #endif
#if defined(__linux__) && defined(PASTA_PATH)
pastaPath.setDefault(PASTA_PATH);
#endif
/* chroot-like behavior from Apple's sandbox */ /* chroot-like behavior from Apple's sandbox */
#ifdef __APPLE__ #ifdef __APPLE__
for (PathView p : { for (PathView p : {

View file

@ -1372,6 +1372,21 @@ public:
Default is 0, which disables the warning. Default is 0, which disables the warning.
Set it to 1 to warn on all paths. Set it to 1 to warn on all paths.
)"}; )"};
#ifdef __linux__
Setting<Path> pastaPath{
this,
"",
"pasta-path",
R"(
If set to an absolute path, enables fully sandboxing fixed-output
derivations, by using `pasta` to pass network traffic between the
private network namespace. This allows for greater levels of isolation
of builds to the host.
)",
{},
false};
#endif
}; };
// FIXME: don't use a global variable. // FIXME: don't use a global variable.

View file

@ -259,6 +259,15 @@ configdata_priv.set_quoted(
: 'lsof', : 'lsof',
) )
# Find pasta for network isolation
if host_machine.system() == 'linux'
pasta_path = get_option('pasta-path')
pasta = find_program(pasta_path, required : false, native : false)
if pasta.found()
configdata_priv.set_quoted('PASTA_PATH', pasta.full_path())
endif
endif
config_priv_h = configure_file( config_priv_h = configure_file(
configuration : configdata_priv, configuration : configdata_priv,
output : 'store-config-private.hh', output : 'store-config-private.hh',

View file

@ -33,3 +33,10 @@ option(
value : '/nix/var/log/nix', value : '/nix/var/log/nix',
description : 'path to store logs in for Nix', description : 'path to store logs in for Nix',
) )
option(
'pasta-path',
type : 'string',
value : 'pasta',
description : 'Path to the location of pasta (provided by passt)',
)

View file

@ -15,6 +15,7 @@
sqlite, sqlite,
busybox-sandbox-shell ? null, busybox-sandbox-shell ? null,
passt ? null,
# Configuration Options # Configuration Options
@ -77,6 +78,9 @@ mkMesonLibrary (finalAttrs: {
] ]
++ lib.optionals stdenv.hostPlatform.isLinux [ ++ lib.optionals stdenv.hostPlatform.isLinux [
(lib.mesonOption "sandbox-shell" "${busybox-sandbox-shell}/bin/busybox") (lib.mesonOption "sandbox-shell" "${busybox-sandbox-shell}/bin/busybox")
]
++ [
(lib.mesonOption "pasta-path" "${passt}/bin/pasta")
]; ];
meta = { meta = {

View file

@ -1,9 +1,13 @@
#ifdef __linux__ #ifdef __linux__
# include "nix/store/globals.hh"
# include "nix/store/personality.hh" # include "nix/store/personality.hh"
# include "nix/util/cgroup.hh" # include "nix/util/cgroup.hh"
# include "nix/util/file-system.hh"
# include "nix/util/linux-namespaces.hh" # include "nix/util/linux-namespaces.hh"
# include "nix/util/strings.hh"
# include "linux/fchmodat2-compat.hh" # include "linux/fchmodat2-compat.hh"
# include "pasta.hh"
# include <sys/ioctl.h> # include <sys/ioctl.h>
# include <net/if.h> # include <net/if.h>
@ -190,6 +194,16 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
*/ */
std::optional<Path> cgroup; std::optional<Path> cgroup;
/**
* Process ID of pasta, if we're using it for network isolation.
*/
Pid pastaPid;
/**
* Whether pasta was started for this build.
*/
bool runPasta = false;
ChrootLinuxDerivationBuilder( ChrootLinuxDerivationBuilder(
LocalStore & store, std::unique_ptr<DerivationBuilderCallbacks> miscMethods, DerivationBuilderParams params) LocalStore & store, std::unique_ptr<DerivationBuilderCallbacks> miscMethods, DerivationBuilderParams params)
: DerivationBuilderImpl{store, std::move(miscMethods), std::move(params)} : DerivationBuilderImpl{store, std::move(miscMethods), std::move(params)}
@ -198,6 +212,18 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
{ {
} }
~ChrootLinuxDerivationBuilder()
{
// pasta being left around mostly happens when builds are aborted
if (pastaPid) {
try {
pasta::killPasta(pastaPid);
} catch (Error & e) {
// Ignore errors during cleanup
}
}
}
uid_t sandboxUid() uid_t sandboxUid()
{ {
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID();
@ -433,6 +459,19 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
/* Signal the builder that we've updated its user namespace. */ /* Signal the builder that we've updated its user namespace. */
writeFull(userNamespaceSync.writeSide.get(), "1\n"); writeFull(userNamespaceSync.writeSide.get(), "1\n");
userNamespaceSyncDone = true; userNamespaceSyncDone = true;
/* Set up pasta for network isolation if enabled and this is a fixed-output derivation */
bool privateNetwork = derivationType.isSandboxed();
runPasta = !privateNetwork && settings.pastaPath != "" && pathExists("/dev/net/tun");
if (runPasta) {
pastaPid = pasta::setupPasta(
settings.pastaPath,
pid,
buildUser ? std::optional(buildUser->getUID()) : std::nullopt,
buildUser ? std::optional(buildUser->getGID()) : std::nullopt,
usingUserNamespace);
}
} }
void enterChroot() override void enterChroot() override
@ -444,6 +483,10 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
userNamespaceSync.readSide = -1; userNamespaceSync.readSide = -1;
if (runPasta) {
pasta::waitForPastaInterface();
}
if (derivationType.isSandboxed()) { if (derivationType.isSandboxed()) {
/* Initialise the loopback interface. */ /* Initialise the loopback interface. */
@ -532,8 +575,17 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
happens when testing Nix building fixed-output derivations happens when testing Nix building fixed-output derivations
within a pure derivation. */ within a pure derivation. */
for (auto & path : {"/etc/resolv.conf", "/etc/services", "/etc/hosts"}) for (auto & path : {"/etc/resolv.conf", "/etc/services", "/etc/hosts"})
if (pathExists(path)) if (pathExists(path)) {
ss.push_back(path); // Special handling for resolv.conf when using pasta
if (runPasta && std::string(path) == "/etc/resolv.conf") {
// We'll write a modified version instead of bind-mounting
auto resolvConf = readFile(std::string(path));
auto modifiedResolvConf = pasta::rewriteResolvConf(resolvConf);
writeFile(chrootRootDir + "/etc/resolv.conf", modifiedResolvConf);
} else {
ss.push_back(path);
}
}
if (settings.caFile != "") { if (settings.caFile != "") {
Path caFile = settings.caFile; Path caFile = settings.caFile;
@ -691,6 +743,10 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
return; return;
} }
if (pastaPid) {
pasta::killPasta(pastaPid);
}
DerivationBuilderImpl::killSandbox(getStats); DerivationBuilderImpl::killSandbox(getStats);
} }

View file

@ -0,0 +1,161 @@
#ifdef __linux__
# include "pasta.hh"
# include "nix/util/error.hh"
# include "nix/util/file-system.hh"
# include "nix/util/processes.hh"
# include "nix/util/strings.hh"
# include "nix/util/util.hh"
# include "nix/util/fmt.hh"
# include <regex>
# include <sys/socket.h>
# include <net/if.h>
# include <sys/ioctl.h>
# include <unistd.h>
# include <fcntl.h>
# include <linux/capability.h>
# include <netinet/in.h>
# include <sys/types.h>
namespace nix {
namespace pasta {
Pid setupPasta(
const Path & pastaPath,
pid_t pid,
std::optional<uid_t> buildUserId,
std::optional<gid_t> buildGroupId,
bool usingUserNamespace)
{
// Bring up pasta for handling FOD networking. We don't let it daemonize
// itself for process management reasons and kill it manually when done.
Strings args = {
"--quiet", "--foreground", "--config-net", "--gateway", PASTA_HOST_IPV4, "--address",
PASTA_CHILD_IPV4, "--netmask", PASTA_IPV4_NETMASK, "--dns-forward", PASTA_HOST_IPV4, "--gateway",
PASTA_HOST_IPV6, "--address", PASTA_CHILD_IPV6, "--dns-forward", PASTA_HOST_IPV6, "--ns-ifname",
PASTA_NS_IFNAME, "--no-netns-quit", "--netns", "/proc/self/fd/0",
};
AutoCloseFD netns(open(fmt("/proc/%i/ns/net", pid).c_str(), O_RDONLY | O_CLOEXEC));
if (!netns) {
throw SysError("failed to open netns");
}
AutoCloseFD userns;
if (usingUserNamespace) {
userns = AutoCloseFD(open(fmt("/proc/%i/ns/user", pid).c_str(), O_RDONLY | O_CLOEXEC));
if (!userns) {
throw SysError("failed to open userns");
}
args.push_back("--userns");
args.push_back("/proc/self/fd/1");
}
// FIXME ideally we want a notification when pasta exits, but we cannot do
// this at present. Without such support we need to busy-wait for pasta to
// set up the namespace completely and time out after a while for the case
// of pasta launch failures. Pasta logs go to syslog only for now as well.
// Use startProcess to launch pasta with proper file descriptor setup
return startProcess([&]() {
// Set up file descriptor redirections
if (dup2(netns.get(), 0) == -1)
throw SysError("cannot redirect netns fd to stdin");
if (userns) {
if (dup2(userns.get(), 1) == -1)
throw SysError("cannot redirect userns fd to stdout");
}
// Set user/group if specified
if (buildGroupId && setgid(*buildGroupId) == -1)
throw SysError("setgid failed");
if (buildUserId && setuid(*buildUserId) == -1)
throw SysError("setuid failed");
// Execute pasta
Strings allArgs = {pastaPath};
allArgs.insert(allArgs.end(), args.begin(), args.end());
execvp(pastaPath.c_str(), stringsToCharPtrs(allArgs).data());
throw SysError("executing pasta");
});
}
void waitForPastaInterface()
{
// Wait for the pasta interface to appear. pasta can't signal us when
// it's done setting up the namespace, so we have to wait for a while
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd)
throw SysError("cannot open IP socket");
struct ifreq ifr;
strcpy(ifr.ifr_name, PASTA_NS_IFNAME);
// Wait two minutes for the interface to appear. If it does not do so
// we are either grossly overloaded, or pasta startup failed somehow.
static constexpr int SINGLE_WAIT_US = 1000;
static constexpr int TOTAL_WAIT_US = 120'000'000;
for (unsigned tries = 0;; tries++) {
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
throw Error(
"sandbox network setup timed out, please check daemon logs for "
"possible error output.");
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
break;
}
} else if (errno == ENODEV) {
usleep(SINGLE_WAIT_US);
} else {
throw SysError("cannot get loopback interface flags");
}
}
}
std::string rewriteResolvConf(const std::string & fromHost)
{
static constexpr auto flags = std::regex::ECMAScript | std::regex::multiline;
static std::regex lineRegex("^nameserver\\s.*$", flags);
static std::regex v4Regex("^nameserver\\s+\\d{1,3}\\.", flags);
static std::regex v6Regex("^nameserver.*:", flags);
std::string nsInSandbox = "\n";
if (std::regex_search(fromHost, v4Regex)) {
nsInSandbox += nix::fmt("nameserver %s\n", PASTA_HOST_IPV4);
}
if (std::regex_search(fromHost, v6Regex)) {
nsInSandbox += nix::fmt("nameserver %s\n", PASTA_HOST_IPV6);
}
return std::regex_replace(fromHost, lineRegex, "") + nsInSandbox;
}
void killPasta(Pid & pastaPid)
{
// FIXME we really want to send SIGTERM instead and wait for pasta to exit,
// but we do not have the infra for that right now. We send SIGKILL instead
// and treat exiting with that as a successful exit code until such a time.
// This is not likely to cause problems since pasta runs as the build user,
// but not inside the build sandbox. If it's killed it's either due to some
// external influence (in which case the sandboxed child will probably fail
// due to network errors, if it used the network at all) or some bug in nix
if (auto status = pastaPid.kill(); !WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
if (WIFSIGNALED(status)) {
throw Error("pasta killed by signal %i", WTERMSIG(status));
} else if (WIFEXITED(status)) {
throw Error("pasta exited with code %i", WEXITSTATUS(status));
} else {
throw Error("pasta exited with status %i", status);
}
}
}
} // namespace pasta
} // namespace nix
#endif // __linux__

View file

@ -0,0 +1,78 @@
#pragma once
#ifdef __linux__
# include "nix/util/processes.hh"
# include "nix/util/file-descriptor.hh"
# include <string>
# include <set>
namespace nix {
/**
* Pasta (Plug A Simple Socket Transport) network isolation constants and utilities.
*
* Pasta provides network isolation for fixed-output derivations by creating
* a Layer-2 to Layer-4 translation without requiring special privileges.
*/
namespace pasta {
// Network configuration constants
// NOTE: These are all C strings because macOS doesn't have constexpr std::string
// constructors, and std::string_view is a pain to turn into std::strings again.
static constexpr const char * PASTA_NS_IFNAME = "eth0";
static constexpr const char * PASTA_HOST_IPV4 = "169.254.1.1";
static constexpr const char * PASTA_CHILD_IPV4 = "169.254.1.2";
static constexpr const char * PASTA_IPV4_NETMASK = "16";
// Randomly chosen 6to4 prefix, mapping the same ipv4ll as above.
// Even if this id is used on the daemon host there should not be
// any collisions since ipv4ll should never be addressed by ipv6.
static constexpr const char * PASTA_HOST_IPV6 = "64:ff9b:1:4b8e:472e:a5c8:a9fe:0101";
static constexpr const char * PASTA_CHILD_IPV6 = "64:ff9b:1:4b8e:472e:a5c8:a9fe:0102";
/**
* Launch pasta for network isolation of a build process.
*
* @param pastaPath Path to the pasta executable
* @param pid Process ID of the build process to isolate
* @param buildUserId Optional UID to run pasta as
* @param buildGroupId Optional GID to run pasta as
* @param usingUserNamespace Whether the build is using a user namespace
* @return Process ID of the pasta process
*/
Pid setupPasta(
const Path & pastaPath,
pid_t pid,
std::optional<uid_t> buildUserId,
std::optional<gid_t> buildGroupId,
bool usingUserNamespace);
/**
* Wait for pasta to set up the network interface.
*
* @throws Error if the interface doesn't appear within the timeout period
*/
void waitForPastaInterface();
/**
* Rewrite /etc/resolv.conf for pasta-isolated builds.
*
* Replaces nameserver entries with pasta's DNS forwarders.
*
* @param fromHost The original resolv.conf content from the host
* @return Modified resolv.conf content for the sandboxed build
*/
std::string rewriteResolvConf(const std::string & fromHost);
/**
* Kill the pasta process.
*
* @param pastaPid The pasta process to kill
* @throws Error if pasta exits with unexpected status
*/
void killPasta(Pid & pastaPid);
} // namespace pasta
} // namespace nix
#endif // __linux__

View file

@ -6,4 +6,16 @@ sources += files(
'user-lock.cc', 'user-lock.cc',
) )
if host_machine.system() == 'linux'
sources += files(
'build/pasta.cc',
)
endif
if host_machine.system() == 'darwin'
sources += files(
'build/darwin-derivation-builder.cc',
)
endif
subdir('include/nix/store') subdir('include/nix/store')

View file

@ -10,6 +10,7 @@ coreutils=@coreutils@
dot=@dot@ dot=@dot@
busybox="@sandbox_shell@" busybox="@sandbox_shell@"
pasta_path=@pasta_path@
version=@PACKAGE_VERSION@ version=@PACKAGE_VERSION@
system=@system@ system=@system@

View file

@ -21,6 +21,7 @@ busybox = find_program('busybox', native : true, required : false)
# guaranteed to exist either. # guaranteed to exist either.
coreutils = find_program('ls', native : true) coreutils = find_program('ls', native : true)
dot = find_program('dot', native : true, required : false) dot = find_program('dot', native : true, required : false)
pasta_path = find_program('pasta', native : false, required : false)
nix_bin_dir = fs.parent(nix.full_path()) nix_bin_dir = fs.parent(nix.full_path())
@ -35,6 +36,15 @@ test_confdata = {
'PACKAGE_VERSION' : meson.project_version(), 'PACKAGE_VERSION' : meson.project_version(),
'system' : nix_system_cpu + '-' + host_machine.system(), 'system' : nix_system_cpu + '-' + host_machine.system(),
} }
if pasta_path.found()
test_confdata += {
'pasta_path': pasta_path.full_path(),
}
else
test_confdata += {
'pasta_path': '',
}
endif
# Just configures `common/vars-and-functions.sh.in`. # Just configures `common/vars-and-functions.sh.in`.
# Done as a subdir() so Meson places it under `common` in the build directory as well. # Done as a subdir() so Meson places it under `common` in the build directory as well.
@ -127,6 +137,7 @@ suites = [
'misc.sh', 'misc.sh',
'dump-db.sh', 'dump-db.sh',
'linux-sandbox.sh', 'linux-sandbox.sh',
'pasta-network.sh',
'supplementary-groups.sh', 'supplementary-groups.sh',
'build-dry.sh', 'build-dry.sh',
'structured-attrs.sh', 'structured-attrs.sh',

View file

@ -12,6 +12,7 @@
mercurial, mercurial,
util-linux, util-linux,
unixtools, unixtools,
passt,
nix-store, nix-store,
nix-expr, nix-expr,
@ -56,7 +57,8 @@ mkMesonDerivation (
git git
mercurial mercurial
unixtools.script unixtools.script
] passt
]
++ lib.optionals stdenv.hostPlatform.isLinux [ ++ lib.optionals stdenv.hostPlatform.isLinux [
# For various sandboxing tests that needs a statically-linked shell, # For various sandboxing tests that needs a statically-linked shell,
# etc. # etc.

133
tests/functional/pasta-network.sh Executable file
View file

@ -0,0 +1,133 @@
#!/usr/bin/env bash
source common.sh
# This test requires Linux sandbox support and pasta
needLocalStore "the sandbox only runs on the builder side"
requireSandboxSupport
requiresUnprivilegedUserNamespaces
# Skip test if pasta is not configured or available
PASTA_PATH="${pasta_path:-}"
if [[ -z "$PASTA_PATH" ]] || [[ "$PASTA_PATH" == "pasta" ]] || [[ ! -x "$PASTA_PATH" ]]; then
skipTest "pasta is not available (pasta_path=$PASTA_PATH)"
fi
# Ensure pasta is in a standard location that Nix can access
# If pasta is in a non-standard location, we need to add it to sandbox-paths
PASTA_DIR=$(dirname "$PASTA_PATH")
export NIX_SANDBOX_PATHS="$PASTA_DIR=$PASTA_DIR"
# Skip test if /dev/net/tun is not available (required for pasta)
if [[ ! -e /dev/net/tun ]]; then
skipTest "/dev/net/tun not available"
fi
clearStore
# Test that fixed-output derivations can access the network when pasta is enabled
echo 'testing fixed-output derivation with network access...'
# Create a test derivation that tries to access the network
cat > pasta-test.nix <<'EOF'
with import ./config.nix;
{
# Test basic network functionality with a fixed-output derivation
testNetworkAccess = mkDerivation {
name = "test-network-access";
builder = builtins.toFile "builder.sh" ''
${bash}/bin/bash -c '
# Test basic network connectivity
# Try to resolve a hostname
if getent hosts localhost >/dev/null 2>&1; then
echo "DNS resolution works"
else
echo "DNS resolution failed"
exit 1
fi
# Test if we can see network interfaces
if ${coreutils}/bin/test -e /sys/class/net/eth0; then
echo "Network interface eth0 exists"
else
echo "Network interface eth0 missing"
exit 1
fi
# Create output
echo "Network test passed" > $out
'
'';
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = "sha256-YCa7ssqLHbdFkPJEG4REJJbsZF9g3w1i+Eg21nUYCCk=";
};
# Test that non-fixed-output derivations cannot access the network
testNoNetworkAccess = mkDerivation {
name = "test-no-network-access";
builder = builtins.toFile "builder.sh" ''
${bash}/bin/bash -c '
# This should fail because non-fixed-output derivations
# should not have network access
if getent hosts localhost >/dev/null 2>&1; then
echo "ERROR: DNS resolution works but should not!"
exit 1
fi
# There should be no network interfaces
if ${coreutils}/bin/test -e /sys/class/net/eth0; then
echo "ERROR: Network interface exists but should not!"
exit 1
fi
echo "Network properly isolated" > $out
'
'';
};
}
EOF
# Test with pasta enabled
echo "Setting pasta-path for network isolation..."
NIX_CONFIG="pasta-path = $PASTA_PATH
sandbox-paths = $NIX_SANDBOX_PATHS" \
nix-build pasta-test.nix -A testNetworkAccess --no-out-link
# Test that non-fixed-output derivations are still isolated
echo "Testing non-fixed-output derivation isolation..."
nix-build pasta-test.nix -A testNoNetworkAccess --no-out-link
# Test that pasta process is properly cleaned up
echo "Testing pasta process cleanup..."
cat > pasta-cleanup-test.nix <<'EOF'
with import ./config.nix;
mkDerivation {
name = "pasta-cleanup-test";
builder = builtins.toFile "builder.sh" ''
${bash}/bin/bash -c '
# Just create output
echo "test" > $out
'
'';
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = "sha256-n4xS51kG4lw0bKJl5VUkJptBS0EbV8LPHZkFV3RJQBU=";
}
EOF
# Build with pasta and check that no pasta processes remain
PASTA_COUNT_BEFORE=$(pgrep -c pasta || echo 0)
NIX_CONFIG="pasta-path = $PASTA_PATH
sandbox-paths = $NIX_SANDBOX_PATHS" \
nix-build pasta-cleanup-test.nix --no-out-link
PASTA_COUNT_AFTER=$(pgrep -c pasta || echo 0)
if [[ $PASTA_COUNT_AFTER -gt $PASTA_COUNT_BEFORE ]]; then
echo "ERROR: pasta process was not cleaned up properly"
exit 1
fi
echo "pasta network isolation tests passed!"

View file

@ -0,0 +1,57 @@
with import ./config.nix;
{
# Test basic network functionality with a fixed-output derivation
testNetworkAccess = mkDerivation {
name = "test-network-access";
builder = builtins.toFile "builder.sh" ''
${bash}/bin/bash -c '
# Test basic network connectivity
# Try to resolve a hostname
if getent hosts localhost >/dev/null 2>&1; then
echo "DNS resolution works"
else
echo "DNS resolution failed"
exit 1
fi
# Test if we can see network interfaces
if ${coreutils}/bin/test -e /sys/class/net/eth0; then
echo "Network interface eth0 exists"
else
echo "Network interface eth0 missing"
exit 1
fi
# Create output
echo "Network test passed" > $out
'
'';
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = "sha256-YCa7ssqLHbdFkPJEG4REJJbsZF9g3w1i+Eg21nUYCCk=";
};
# Test that non-fixed-output derivations cannot access the network
testNoNetworkAccess = mkDerivation {
name = "test-no-network-access";
builder = builtins.toFile "builder.sh" ''
${bash}/bin/bash -c '
# This should fail because non-fixed-output derivations
# should not have network access
if getent hosts localhost >/dev/null 2>&1; then
echo "ERROR: DNS resolution works but should not!"
exit 1
fi
# There should be no network interfaces
if ${coreutils}/bin/test -e /sys/class/net/eth0; then
echo "ERROR: Network interface exists but should not!"
exit 1
fi
echo "Network properly isolated" > $out
'
'';
};
}

View file

@ -0,0 +1,93 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "nix/store/globals.hh"
#include "nix/util/processes.hh"
#include "nix/util/file-system.hh"
#ifdef __linux__
#include "nix/unix/build/pasta.hh"
namespace nix {
TEST(PastaTest, ConstantsAreDefined) {
// Test that all pasta constants are properly defined
EXPECT_STREQ(pasta::PASTA_NS_IFNAME, "eth0");
EXPECT_STREQ(pasta::PASTA_HOST_IPV4, "169.254.1.1");
EXPECT_STREQ(pasta::PASTA_CHILD_IPV4, "169.254.1.2");
EXPECT_STREQ(pasta::PASTA_PREFIX_IPV4, "30");
}
TEST(PastaTest, RewriteResolvConfBasic) {
// Test basic resolv.conf rewriting
std::string original = R"(
# Generated by NetworkManager
nameserver 8.8.8.8
nameserver 8.8.4.4
search example.com
)";
std::string expected = R"(
# Generated by NetworkManager
nameserver 169.254.1.1
nameserver 169.254.1.1
search example.com
)";
std::string result = pasta::rewriteResolvConf(original);
EXPECT_EQ(result, expected);
}
TEST(PastaTest, RewriteResolvConfEmpty) {
// Test empty resolv.conf
std::string original = "";
std::string result = pasta::rewriteResolvConf(original);
EXPECT_EQ(result, "");
}
TEST(PastaTest, RewriteResolvConfComments) {
// Test resolv.conf with only comments
std::string original = R"(# This is a comment
# Another comment
)";
std::string result = pasta::rewriteResolvConf(original);
EXPECT_EQ(result, original);
}
TEST(PastaTest, RewriteResolvConfMixed) {
// Test resolv.conf with various directives
std::string original = R"(
nameserver 192.168.1.1
domain local.lan
search local.lan corp.lan
nameserver 192.168.1.2
options ndots:1
)";
std::string expected = R"(
nameserver 169.254.1.1
domain local.lan
search local.lan corp.lan
nameserver 169.254.1.1
options ndots:1
)";
std::string result = pasta::rewriteResolvConf(original);
EXPECT_EQ(result, expected);
}
TEST(PastaTest, SettingIsConfigurable) {
// Test that pasta path setting can be configured
Settings settings;
// Default should be empty
EXPECT_EQ(settings.pastaPath.get(), "");
// Should be settable
settings.pastaPath = "/usr/bin/pasta";
EXPECT_EQ(settings.pastaPath.get(), "/usr/bin/pasta");
}
} // namespace nix
#endif // __linux__