I developed a system to automatically generate hardening configuration for
systemd services^1.
The idea is to clone nixpkgs, and instrument each test using an object I called
systemdPassthru
(I am very open to suggestions for better names), that I can use
to manipulate configuration options.
In practice, a service like this
{ pkgs, ...}:
{
options = ...;
config.systemd.services.myservice.serviceConfig = {
Foo = "Bar";
};
}
becomes this
{ pkgs, systemdPassthru ...}:
{
options = ...;
config.systemd.services.myservice.serviceConfig = {
Foo = "Bar";
PrivateDevices = systemdPassthru.myservice.PrivateDevices;
};
}
This transformation is done autmatically by a tool I wrote in Rust using rnix^2.
In order to make this work, I have do modify two things in nixpkgs:
*** nixpkgs/nixos/tests/make-test-python.nix
--- nixpkgs/nixos/tests/make-test-python.nix -- "hooked" version
***************
*** 1,9 ****
f: {
system ? builtins.currentSystem,
pkgs ? import ../.. { inherit system; },
...
} @ args:
! with import ../lib/testing-python.nix { inherit system pkgs; };
makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
--- 1,10 ----
f: {
+ systemdPassthru,
system ? builtins.currentSystem,
pkgs ? import ../.. { inherit system; },
...
} @ args:
! with import ../lib/testing-python.nix { inherit system pkgs; specialArgs = { inherit systemdPassthru; }; };
makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
*** output/nixpkgs/nixos/tests/all-tests.nix
--- output/nixpkgs/nixos/tests/all-tests.nix -- "hooked" version
***************
*** 1,4 ****
! { system, pkgs, callTest }:
# The return value of this function will be an attrset with arbitrary depth and
# the `anything` returned by callTest at its test leafs.
# The tests not supported by `system` will be replaced with `{}`, so that
--- 1,4 ----
! { systemdPassthru, system, pkgs, callTest }:
# The return value of this function will be an attrset with arbitrary depth and
# the `anything` returned by callTest at its test leafs.
# The tests not supported by `system` will be replaced with `{}`, so that
***************
*** 15,21 ****
else if hasAttr "test" val then callTest val
else mapAttrs (n: s: discoverTests s) val;
handleTest = path: args:
! discoverTests (import path ({ inherit system pkgs; } // args));
handleTestOn = systems: path: args:
if elem system systems then handleTest path args
else {};
--- 15,21 ----
else if hasAttr "test" val then callTest val
else mapAttrs (n: s: discoverTests s) val;
handleTest = path: args:
! discoverTests (import path ({ inherit systemdPassthru system pkgs; } // args));
handleTestOn = systems: path: args:
if elem system systems then handleTest path args
else {};
But some tests donāt work well with this pattern (54 out of 564).
To make systemdPassthru
work, I used specialArgs
.
Iām wondering if the right thing to do is to make every single test accept specialArgs
.
This would allow for some modularity in test instantiation:
let myTests = import <nixpkgs/nixos/tests/all-tests.nix> {
specialArgs = {
# Options I pass to some module that expects some special config.
};
system = builtins.currentSystem;
pkgs = import <nixpkgs> { system = builtins.currentSystem; };
callTest = t: lib.hydraJob t.test;
};
This is very useful in my case, but even I think the usefulness of this pattern is
arguable outside the scope of my work.
Iām also wondering if the right approach isnāt actually to use _module
instead of specialArgs
.
In any case, I have to make my generation system work, so Iām looking for some advice
to choose between these options:
- Either do this on my side, make a local branch where I modify the 54 misbehaving tests
and usegit mail
to generate pathches, and add a patch phase to
the workflow ofnixos-harden-systemd
This option would trivially work, but it does not feel very satisfactory, at least to me. - Do about the same thing, but make every test accept
specialArgs
, and commit the result
in nixpkgs. Then derive my special case from this.
Or exactly that, but with_module
instead ofspecialArgs
, or with both_module
andspecialArgs
.
What does feel right to you?
Iām of course open to any other suggestion, and willing to discuss this further and give
more information / explain any unclear point of what Iāve done.
This project is funded by the European Commission through nlnet, and I am really grateful to them for the opportunity!