Add root-aware privileged CI test lane
All checks were successful
smoke / smoke (push) Successful in 29s
All checks were successful
smoke / smoke (push) Successful in 29s
This commit is contained in:
@@ -37,6 +37,43 @@ jobs:
|
||||
gcc --version | head -n 1
|
||||
pkg-config --modversion libnftables
|
||||
|
||||
- name: Detect runner mode
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
uid="$(id -u)"
|
||||
echo "RUNNER_UID=$uid" >> "$GITHUB_ENV"
|
||||
|
||||
cap_net_admin=0
|
||||
if [ -r /proc/self/status ]; then
|
||||
cap_eff_hex="$(awk '/^CapEff:/ { print $2 }' /proc/self/status)"
|
||||
if [ -n "${cap_eff_hex:-}" ]; then
|
||||
cap_eff_value=$((16#$cap_eff_hex))
|
||||
if (( (cap_eff_value & (1 << 12)) != 0 )); then
|
||||
cap_net_admin=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "RUNNER_HAS_CAP_NET_ADMIN=$cap_net_admin" >> "$GITHUB_ENV"
|
||||
|
||||
if [ "$uid" -eq 0 ]; then
|
||||
echo "RUNNER_IS_ROOT=1" >> "$GITHUB_ENV"
|
||||
echo "Root runner detected (uid 0)."
|
||||
|
||||
if [ "$cap_net_admin" -eq 1 ]; then
|
||||
echo "RUN_PRIVILEGED_TESTS=1" >> "$GITHUB_ENV"
|
||||
echo "Privileged test lane enabled."
|
||||
else
|
||||
echo "RUN_PRIVILEGED_TESTS=0" >> "$GITHUB_ENV"
|
||||
echo "::warning::Root runner detected, but CAP_NET_ADMIN is unavailable. Privileged tests will be skipped."
|
||||
fi
|
||||
else
|
||||
echo "RUNNER_IS_ROOT=0" >> "$GITHUB_ENV"
|
||||
echo "RUN_PRIVILEGED_TESTS=0" >> "$GITHUB_ENV"
|
||||
echo "Non-root runner detected; smoke-only test lane enabled."
|
||||
fi
|
||||
|
||||
- name: Install .NET SDK
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -58,7 +95,13 @@ jobs:
|
||||
set -euo pipefail
|
||||
"$HOME/.dotnet/dotnet" build --no-restore
|
||||
|
||||
- name: Test
|
||||
- name: Smoke tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
"$HOME/.dotnet/dotnet" test LibNftables.slnx --no-build
|
||||
LIBNFTABLES_RUN_PRIVILEGED_TESTS=0 "$HOME/.dotnet/dotnet" test LibNftables.slnx --no-build --filter "Category!=Privileged"
|
||||
|
||||
- name: Privileged tests
|
||||
if: env.RUN_PRIVILEGED_TESTS == '1'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
LIBNFTABLES_RUN_PRIVILEGED_TESTS=1 "$HOME/.dotnet/dotnet" test LibNftables.slnx --no-build --filter "Category=Privileged"
|
||||
|
||||
17
README.md
17
README.md
@@ -61,7 +61,7 @@ The test suite contains:
|
||||
|
||||
- Managed/unit tests that do not require a native runtime
|
||||
- Native integration tests that self-gate when `libnftables` is unavailable
|
||||
- Capability-dependent tests that only run when `CAP_NET_ADMIN` is available
|
||||
- Privileged tests that only run when `LIBNFTABLES_RUN_PRIVILEGED_TESTS=1`, `uid == 0`, and `CAP_NET_ADMIN` is available
|
||||
|
||||
## Gitea Smoke CI
|
||||
|
||||
@@ -69,12 +69,13 @@ The repository includes a Gitea Actions smoke workflow at `.gitea/workflows/smok
|
||||
|
||||
- Trigger: push and pull request
|
||||
- Runner label: `debian-13`
|
||||
- Job model: non-root smoke verification
|
||||
- Job model: root-aware smoke verification
|
||||
- Workflow actions:
|
||||
- bootstrap the .NET 10 SDK inside the job
|
||||
- restore
|
||||
- build
|
||||
- run `dotnet test`
|
||||
- always run the non-privileged smoke test lane
|
||||
- run the privileged test lane when the runner process is `uid 0`
|
||||
|
||||
Important runner prerequisites:
|
||||
|
||||
@@ -85,7 +86,15 @@ Important runner prerequisites:
|
||||
- `pkg-config`
|
||||
- system-installed `libnftables` development/runtime packages discoverable by `pkg-config`
|
||||
|
||||
The job does not attempt package-manager installs or privilege escalation. It is intended to catch restore/build/test regressions, not to provide privileged nftables coverage.
|
||||
The job does not attempt package-manager installs or privilege escalation. It is intended to catch restore/build/test regressions with opportunistic privileged smoke coverage on root runners.
|
||||
|
||||
The workflow auto-detects `id -u` at runtime:
|
||||
|
||||
- non-root runners stay on the smoke-only path
|
||||
- root runners enable `LIBNFTABLES_RUN_PRIVILEGED_TESTS=1`
|
||||
- root runners without effective `CAP_NET_ADMIN` emit a warning and skip the privileged lane
|
||||
|
||||
For local runs, `dotnet test` keeps privileged tests disabled by default. To opt in intentionally, set `LIBNFTABLES_RUN_PRIVILEGED_TESTS=1` in an environment where the process is running as root with `CAP_NET_ADMIN`.
|
||||
|
||||
## High-Level Example
|
||||
|
||||
|
||||
@@ -4,6 +4,58 @@ namespace LibNftables.Tests;
|
||||
|
||||
internal static class NativeTestSupport
|
||||
{
|
||||
private const string PrivilegedTestsEnvironmentVariable = "LIBNFTABLES_RUN_PRIVILEGED_TESTS";
|
||||
|
||||
internal static bool IsRunningAsRoot()
|
||||
{
|
||||
if (!OperatingSystem.IsLinux())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var line in File.ReadLines("/proc/self/status"))
|
||||
{
|
||||
if (!line.StartsWith("Uid:", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = line["Uid:".Length..]
|
||||
.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
return parts.Length > 0 && parts[0] == "0";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If uid probing fails, keep tests conservative.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool PrivilegedTestsRequested()
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(PrivilegedTestsEnvironmentVariable);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.Equals("1", StringComparison.Ordinal)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
internal static bool ShouldRunPrivilegedTests()
|
||||
{
|
||||
return PrivilegedTestsRequested()
|
||||
&& IsRunningAsRoot()
|
||||
&& HasCapNetAdmin();
|
||||
}
|
||||
|
||||
internal static bool HasCapNetAdmin()
|
||||
{
|
||||
const int capNetAdminBit = 12;
|
||||
|
||||
@@ -53,6 +53,7 @@ public sealed class NftContextTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Privileged")]
|
||||
public void ValidDryRunCommand_CanExecuteAndBufferOutput()
|
||||
{
|
||||
if (!NativeTestSupport.TryCreateContext(out var ctx))
|
||||
@@ -60,7 +61,7 @@ public sealed class NftContextTests
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NativeTestSupport.HasCapNetAdmin())
|
||||
if (!NativeTestSupport.ShouldRunPrivilegedTests())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,27 @@ public sealed class NftablesClientIntegrationTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Privileged")]
|
||||
public void Snapshot_WithPrivilegedLane_ReturnsRuleset()
|
||||
{
|
||||
if (!CanCreateClient())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NativeTestSupport.ShouldRunPrivilegedTests())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var client = new NftablesClient();
|
||||
|
||||
NftSnapshot snapshot = client.Snapshot();
|
||||
|
||||
Assert.False(string.IsNullOrWhiteSpace(snapshot.RulesetText));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateRuleset_WithTypedSetDefinition_ReturnsValidResult()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user