nixos/netbox: uplift (#531455)

This commit is contained in:
Rémi NICOLE
2026-07-10 17:22:08 +00:00
committed by GitHub
5 changed files with 688 additions and 374 deletions
@@ -97,6 +97,16 @@
- The `shell_interact()` function on interactive runs of NixOS VM tests has been deprecated. Use the SSH backdoor instead.
- [services.netbox](#opt-services.netbox.enable) has received a number of updates:
- Default settings can now be introspected at [](#opt-services.netbox.settings).
- Environment files can now be passed at [](#opt-services.netbox.environmentFiles).
- When Django [secret key](#opt-services.netbox.secretKeyFile) or [API token peppers](#opt-services.netbox.apiTokenPepperFiles)
remain unset, random values will automatically be generated and stored below `/var/lib/netbox`.
- Multiple peppers can now be maintained, which allows for pepper rotation.
- All options to bind the gunicorn socket have been unified in [](#opt-services.netbox.bind)
and the default changed to a UNIX domain socket.
- A cookie-cutter nginx vhost can be enabled at [](#opt-services.netbox.nginx.enable).
- `security.run0.enableSudoAlias` now uses the `run0-sudo-shim` instead of a shell-script to improve compatibility.
- With `system.etc.overlay.mutable = false`, NixOS now ships an empty `/etc/machine-id` in the image. Previously the file was absent and systemd logged `System cannot boot: Missing /etc/machine-id and /etc/ is read-only` while `ConditionFirstBoot` fired on every boot. With this change, systemd now overlays a transient ID from `/run/machine-id` for the session, and `systemd-machine-id-commit.service` has `ConditionFirstBoot` so it writes the machine-id through to a persistent backing file when one is bind-mounted over `/etc/machine-id`. To persist the machine-id across reboots, bind-mount a writable file containing `uninitialized` over `/etc/machine-id` from the initrd, or set `systemd.machine_id=` on the kernel command line (use `systemd.machine_id=firmware` to derive a stable ID on hardware that supports it).
File diff suppressed because it is too large Load Diff
+4 -17
View File
@@ -34,23 +34,10 @@ in
# Pick the NetBox package from this config's "pkgs" argument,
# so that `nixpkgs.config.permittedInsecurePackages` works
package = pkgs.${oldNetbox};
secretKeyFile = pkgs.writeText "secret" ''
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
'';
apiTokenPeppersFile = pkgs.writeText "pepper" ''
kp7ht*76fiQAhUi5dHfASLlYUE_S^gI^(7J^K5M!LfoH@vl&b_
'';
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts.netbox = {
default = true;
locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
locations."/static/".alias = "/var/lib/netbox/static/";
nginx = {
enable = true;
hostname = "localhost";
};
};
@@ -81,7 +68,7 @@ in
headers = machine.succeed(
"curl -sSL http://localhost/api/ --head -H 'Content-Type: application/json'"
)
assert api_version(headers) == version
t.assertEqual(api_version(headers), version)
with subtest("NetBox version is the old one"):
check_api_version("${oldApiVersion}")
+11 -23
View File
@@ -27,23 +27,23 @@ import ../../make-test-python.nix (
skipTypeCheck = true;
nodes.machine =
containers.machine =
{ config, ... }:
{
virtualisation.memorySize = 2048;
boot.kernelParams = [
# helps debugging seccomp filter issues
"audit=1"
];
services.netbox = {
enable = true;
package = netbox;
secretKeyFile = pkgs.writeText "secret" ''
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
'';
# Value from https://netbox.readthedocs.io/en/feature/configuration/required-parameters/#api_token_peppers
apiTokenPeppersFile = pkgs.writeText "pepper" ''
kp7ht*76fiQAhUi5dHfASLlYUE_S^gI^(7J^K5M!LfoH@vl&b_
'';
enableLdap = true;
ldapConfigPath = pkgs.writeText "ldap_config.py" ''
nginx = {
enable = true;
hostname = "localhost";
};
ldapConfigFile = pkgs.writeText "ldap_config.py" ''
import ldap
from django_auth_ldap.config import LDAPSearch, PosixGroupType
@@ -70,18 +70,6 @@ import ../../make-test-python.nix (
'';
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts.netbox = {
default = true;
locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
locations."/static/".alias = "/var/lib/netbox/static/";
};
};
# Adapted from the sssd-ldap NixOS test
services.openldap = {
enable = true;
+23 -20
View File
@@ -62,11 +62,11 @@ def compare(a: str, b: str):
with subtest("Home screen loads"):
machine.wait_until_succeeds(
"curl -sSfL http://[::1]:8001 | grep '<title>Home | NetBox</title>'"
"curl -sSfL http://[::1]:80 | grep '<title>Home | NetBox</title>'"
)
with subtest("Staticfiles are generated"):
machine.succeed("test -e /var/lib/netbox/static/netbox.js")
machine.wait_for_file("/var/lib/netbox/static/netbox.js")
with subtest("Superuser can be created"):
machine.succeed(
@@ -155,7 +155,7 @@ def patch(uri: str, data: Dict[str, Any]):
return data_request(uri, "PATCH", data)
with subtest("Can retrieve netbox version"):
assert netbox_version == get("/status/")["netbox-version"]
t.assertEqual(netbox_version, get("/status/")["netbox-version"])
with subtest("Can create objects"):
result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
@@ -196,28 +196,28 @@ with subtest("Can create objects"):
with subtest("Can list objects"):
result = get("/dcim/sites/")
assert result["count"] == 1
assert result["results"][0]["id"] == site_id
assert result["results"][0]["name"] == "Test site"
assert result["results"][0]["description"] == ""
t.assertEqual(result["count"], 1)
t.assertEqual(result["results"][0]["id"], site_id)
t.assertEqual(result["results"][0]["name"], "Test site")
t.assertEqual(result["results"][0]["description"], "")
result = get("/dcim/device-types/")
assert result["count"] == 1
assert result["results"][0]["id"] == device_type_id
assert result["results"][0]["model"] == "Test device type"
t.assertEqual(result["count"], 1)
t.assertEqual(result["results"][0]["id"], device_type_id)
t.assertEqual(result["results"][0]["model"], "Test device type")
with subtest("Can update objects"):
new_description = "Test site description"
patch(f"/dcim/sites/{site_id}/", {"description": new_description})
result = get(f"/dcim/sites/{site_id}/")
assert result["description"] == new_description
t.assertEqual(result["description"], new_description)
with subtest("Can delete objects"):
# Delete a device-type since no object depends on it
delete(f"/dcim/device-types/{device_type_id}/")
result = get("/dcim/device-types/")
assert result["count"] == 0
t.assertEqual(result["count"], 0)
def request_graphql(query: str):
return machine.succeed(
@@ -252,10 +252,10 @@ if compare(netbox_version, '4.2.0') >= 0:
answer = request_graphql(graphql_query)
result = json.loads(answer)
assert len(result["data"]["prefix_list"]) == 3
assert test_objects["prefixes"]["v4-with-updated-desc"] in result["data"]["prefix_list"]
assert test_objects["prefixes"]["v6-cidr-32"] in result["data"]["prefix_list"]
assert test_objects["prefixes"]["v6-cidr-48"] in result["data"]["prefix_list"]
t.assertEqual(len(result["data"]["prefix_list"]), 3)
t.assertIn(test_objects["prefixes"]["v4-with-updated-desc"], result["data"]["prefix_list"])
t.assertIn(test_objects["prefixes"]["v6-cidr-32"], result["data"]["prefix_list"])
t.assertIn(test_objects["prefixes"]["v6-cidr-48"], result["data"]["prefix_list"])
if compare(netbox_version, '4.2.0') < 0:
with subtest("Can use the GraphQL API (Netbox <= 4.2.0)"):
@@ -270,8 +270,8 @@ if compare(netbox_version, '4.2.0') < 0:
''')
result = json.loads(answer)
print(result["data"]["prefix_list"][0])
assert result["data"]["prefix_list"][0]["prefix"] == test_objects["prefixes"]["v4-with-updated-desc"]["prefix"]
assert int(result["data"]["prefix_list"][0]["site"]["id"]) == int(test_objects["prefixes"]["v4-with-updated-desc"]["scope"]["id"])
t.assertEqual(result["data"]["prefix_list"][0]["prefix"], test_objects["prefixes"]["v4-with-updated-desc"]["prefix"])
t.assertEqual(int(result["data"]["prefix_list"][0]["site"]["id"]), int(test_objects["prefixes"]["v4-with-updated-desc"]["scope"]["id"]))
# With 4.5.2 and higher, obtaining a session cookie or token without supplying
# proper CSRF tokens on the frontend /login/ endpoint is no longer possible
@@ -283,5 +283,8 @@ if compare(netbox_version, '4.5.2') < 0:
with subtest("Can associate LDAP groups"):
result = get("/users/users/?username=${testUser}")
assert result["count"] == 1
assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
t.assertEqual(result["count"], 1)
t.assertTrue(any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"]))
# Print systemd unit hardening state
machine.log(machine.execute("systemd-analyze security netbox.service netbox-rq.service netbox-housekeeping.service | grep -v ✓")[1])