netbox_4_5: init at 4.5.5 (#485109)

This commit is contained in:
Rémi NICOLE
2026-03-22 22:28:15 +00:00
committed by GitHub
13 changed files with 118 additions and 192 deletions
+4
View File
@@ -165,6 +165,10 @@
- The `programs.captive-browser` module no longer falls back on a setcap wrapper around udhcpc to discover your network's DNS server due to [GHSA-wc3r-c66x-8xmc](https://github.com/NixOS/nixpkgs/security/advisories/GHSA-wc3r-c66x-8xmc) (CVE-2026-25740). If you're using this module, you must either configure `programs.captive-browser.dhcp-dns` manually or enable one of NetworkManager, dhcpcd, or systemd-networkd.
- NetBox was updated to `>= 4.5.5`. Have a look at the breaking changes
of the [4.5 release](https://github.com/netbox-community/netbox/releases/tag/v4.5.0),
make the required changes to your database, if needed, then upgrade by setting `services.netbox.package = pkgs.netbox_4_5;` in your configuration.
- The `services.yggdrasil` module has been refactored with the following breaking changes:
- The `services.yggdrasil.configFile` option has been removed. Configuration should now be specified directly via `services.yggdrasil.settings`.
- The `services.yggdrasil.persistentKeys` option has been removed. To maintain persistent keys and IPv6 addresses across reboots, use `services.yggdrasil.settings.PrivateKeyPath` to securely load your private key from a file via systemd credentials. The private key must be in PEM format (PKCS #8).
+32 -8
View File
@@ -119,11 +119,11 @@ in
package = lib.mkOption {
type = lib.types.package;
default =
if lib.versionAtLeast config.system.stateVersion "25.11" then pkgs.netbox_4_4 else pkgs.netbox_4_2;
if lib.versionAtLeast config.system.stateVersion "26.05" then pkgs.netbox_4_5 else pkgs.netbox_4_4;
defaultText = lib.literalExpression ''
if lib.versionAtLeast config.system.stateVersion "25.11"
then pkgs.netbox_4_4
else pkgs.netbox_4_2;
if lib.versionAtLeast config.system.stateVersion "26.05"
then pkgs.netbox_4_5
else pkgs.netbox_4_4;
'';
description = ''
NetBox package to use.
@@ -165,6 +165,15 @@ in
'';
};
apiTokenPeppersFile = lib.mkOption {
type = with lib.types; nullOr path;
description = ''
Path to a file containing the API_TOKEN_PEPPER that will be configured for the pepper_id with the id 1.
This is required for netbox >= 4.5. For generating this pepper,
[consider using `$INSTALL_ROOT/netbox/generate_secret_key.py`](https://netboxlabs.com/docs/netbox/configuration/required-parameters/#api_token_peppers)
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
@@ -237,10 +246,12 @@ in
GIT_PATH = "${pkgs.gitMinimal}/bin/git";
DATABASE = {
NAME = "netbox";
USER = "netbox";
HOST = "/run/postgresql";
DATABASES = {
"default" = {
NAME = "netbox";
USER = "netbox";
HOST = "/run/postgresql";
};
};
# Redis database settings. Redis is used for caching and for queuing
@@ -283,6 +294,12 @@ in
with open("${cfg.secretKeyFile}", "r") as file:
SECRET_KEY = file.readline()
''
+ (lib.optionalString (cfg.apiTokenPeppersFile != null) ''
with open("${cfg.apiTokenPeppersFile}", "r") as pepper_file:
API_TOKEN_PEPPERS = {
1: pepper_file.readline(),
};
'')
+ (lib.optionalString (cfg.keycloakClientSecret != null) ''
with open("${cfg.keycloakClientSecret}", "r") as file:
SOCIAL_AUTH_KEYCLOAK_SECRET = file.readline()
@@ -446,5 +463,12 @@ in
};
users.groups.netbox = { };
users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ];
assertions = [
{
assertion = (lib.versionAtLeast cfg.package.version "4.5") -> (cfg.apiTokenPeppersFile != null);
message = "NetBox 4.5 or newer require setting `services.netbox.apiTokenPeppersFile`";
}
];
};
}
+1 -2
View File
@@ -1054,9 +1054,8 @@ in
neo4j = runTest ./neo4j.nix;
netbird = runTest ./netbird.nix;
netbox-upgrade = runTest ./web-apps/netbox-upgrade.nix;
netbox_4_2 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_2; };
netbox_4_3 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_3; };
netbox_4_4 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_4; };
netbox_4_5 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_5; };
netdata = runTest ./netdata.nix;
networking.networkd = handleTest ./networking/networkd-and-scripted.nix { networkd = true; };
networking.networkmanager = handleTest ./networking/networkmanager.nix { };
+5 -2
View File
@@ -1,7 +1,7 @@
{ lib, pkgs, ... }:
let
oldNetbox = "netbox_4_3";
newNetbox = "netbox_4_4";
oldNetbox = "netbox_4_4";
newNetbox = "netbox_4_5";
apiVersion =
version:
@@ -38,6 +38,9 @@ in
secretKeyFile = pkgs.writeText "secret" ''
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
'';
apiTokenPeppersFile = pkgs.writeText "pepper" ''
kp7ht*76fiQAhUi5dHfASLlYUE_S^gI^(7J^K5M!LfoH@vl&b_
'';
};
services.nginx = {
+28 -2
View File
@@ -37,6 +37,10 @@ import ../../make-test-python.nix (
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" ''
@@ -155,10 +159,32 @@ import ../../make-test-python.nix (
u.set_password('netbox')
u.save()
'';
createToken = pkgs.writeText "create-token-v2.py" ''
from users.models import Token, User
from users.choices import TokenVersionChoices
u = User.objects.first()
t = Token.objects.create(user=u, token="0123456789abcdef0123456789abcdef01234567", version=TokenVersionChoices.V2)
print(t.get_auth_header_prefix())
'';
netboxVersion = netbox.version;
in
builtins.replaceStrings
[ "\${changePassword}" "\${testUser}" "\${testPassword}" "\${testGroup}" ]
[ "${changePassword}" "${testUser}" "${testPassword}" "${testGroup}" ]
[
"\${changePassword}"
"\${testUser}"
"\${testPassword}"
"\${testGroup}"
"\${createToken}"
"\${netboxVersion}"
]
[
"${changePassword}"
"${testUser}"
"${testPassword}"
"${testGroup}"
"${createToken}"
"${netboxVersion}"
]
(lib.readFile "${./testScript.py}");
}
)
+36 -23
View File
@@ -73,18 +73,7 @@ with subtest("Superuser can be created"):
"netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com"
)
# Django doesn't have a "clean" way of inputting the password from the command line
machine.succeed("cat '${changePassword}' | netbox-manage shell")
machine.wait_for_unit("network.target")
with subtest("Home screen loads from nginx"):
machine.succeed(
"curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'"
)
with subtest("Staticfiles can be fetched"):
machine.succeed("curl -sSfL http://localhost/static/netbox.js")
machine.succeed("curl -sSfL http://localhost/static/docs/")
machine.succeed("cat '${changePassword}' | netbox-manage shell --interface python")
def login(username: str, password: str):
encoded_data = json.dumps({"username": username, "password": password})
@@ -101,8 +90,29 @@ def login(username: str, password: str):
)
return result["key"]
with subtest("Can login"):
auth_token = login("netbox", "netbox")
netbox_version = "${netboxVersion}"
if compare(netbox_version, '4.5.2') < 0:
with subtest("Can login"):
auth_token = login("netbox", "netbox")
else:
with subtest("Superusertoken can be created"):
full_token = "0123456789abcdef0123456789abcdef01234567"
stdout = machine.succeed("cat ${createToken} | netbox-manage shell")
token_prefix = stdout.split(r"Bearer ")[-1].lstrip().rstrip()
auth_token = f"{token_prefix}{full_token}"
machine.wait_for_unit("network.target")
with subtest("Home screen loads from nginx"):
machine.succeed(
"curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'"
)
with subtest("Staticfiles can be fetched"):
machine.succeed("curl -sSfL http://localhost/static/netbox.js")
machine.succeed("curl -sSfL http://localhost/static/docs/")
def get(uri: str):
return json.loads(
@@ -144,8 +154,8 @@ def post(uri: str, data: Dict[str, Any]):
def patch(uri: str, data: Dict[str, Any]):
return data_request(uri, "PATCH", data)
# Retrieve netbox version
netbox_version = get("/status/")["netbox-version"]
with subtest("Can retrieve netbox version"):
assert netbox_version == get("/status/")["netbox-version"]
with subtest("Can create objects"):
result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
@@ -263,12 +273,15 @@ if compare(netbox_version, '4.2.0') < 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"])
with subtest("Can login with LDAP"):
machine.wait_for_unit("openldap.service")
login("alice", "${testPassword}")
# 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
if compare(netbox_version, '4.5.2') < 0:
with subtest("Can login with LDAP"):
machine.wait_for_unit("openldap.service")
login("alice", "${testPassword}")
with subtest("Can associate LDAP groups"):
result = get("/users/users/?username=${testUser}")
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"])
assert result["count"] == 1
assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
-132
View File
@@ -1,132 +0,0 @@
{
lib,
fetchFromGitHub,
python3,
plugins ? _ps: [ ],
nixosTests,
nix-update-script,
}:
let
py = python3.override {
packageOverrides = _final: prev: { django = prev.django_5; };
};
extraBuildInputs = plugins py.pkgs;
in
py.pkgs.buildPythonApplication rec {
pname = "netbox";
version = "4.2.9";
pyproject = false;
src = fetchFromGitHub {
owner = "netbox-community";
repo = "netbox";
tag = "v${version}";
hash = "sha256-uVe4YTZoxRMBfvItFa9SMHu4AaVvygfAg9GDB115TFc=";
};
patches = [
./custom-static-root.patch
];
propagatedBuildInputs =
(
with py.pkgs;
[
django
django-cors-headers
django-debug-toolbar
django-filter
django-graphiql-debug-toolbar
django-htmx
django-mptt
django-pglocks
django-prometheus
django-redis
django-rq
django-tables2
django-taggit
django-timezone-field
djangorestframework
drf-spectacular
drf-spectacular-sidecar
feedparser
jinja2
markdown
netaddr
nh3
pillow
psycopg
psycopg.optional-dependencies.c
psycopg.optional-dependencies.pool
pyyaml
requests
social-auth-core
social-auth-app-django
strawberry-graphql
strawberry-django
svgwrite
tablib
# Optional dependencies, kept here for backward compatibility
# for the S3 data source backend
boto3
# for Git data source backend
dulwich
# for error reporting
sentry-sdk
]
++ social-auth-core.passthru.optional-dependencies.openidconnect
)
++ extraBuildInputs;
buildInputs = with py.pkgs; [
mkdocs-material
mkdocs-material-extensions
mkdocstrings
mkdocstrings-python
];
nativeBuildInputs = [ py.pkgs.mkdocs ];
postBuild = ''
PYTHONPATH=$PYTHONPATH:netbox/
python -m mkdocs build
'';
installPhase = ''
mkdir -p $out/opt/netbox
cp -r . $out/opt/netbox
chmod +x $out/opt/netbox/netbox/manage.py
makeWrapper $out/opt/netbox/netbox/manage.py $out/bin/netbox \
--prefix PYTHONPATH : "$PYTHONPATH"
'';
passthru = {
python = python3;
# PYTHONPATH of all dependencies used by the package
pythonPath = py.pkgs.makePythonPath propagatedBuildInputs;
inherit (py.pkgs) gunicorn;
tests = {
netbox = nixosTests.netbox_4_2;
inherit (nixosTests) netbox-upgrade;
};
updateScript = nix-update-script { };
};
meta = {
homepage = "https://github.com/netbox-community/netbox";
description = "IP address management (IPAM) and data center infrastructure management (DCIM) tool";
mainProgram = "netbox";
license = lib.licenses.asl20;
knownVulnerabilities = [
"Netbox Version ${version} is EOL; please upgrade by following the current release notes instructions"
];
maintainers = with lib.maintainers; [
minijackson
raitobezarius
];
};
}
@@ -1,13 +0,0 @@
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 2de06dd10..00406af48 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -410,7 +412,7 @@ USE_X_FORWARDED_HOST = True
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Static files (CSS, JavaScript, Images)
-STATIC_ROOT = BASE_DIR + '/static'
+STATIC_ROOT = getattr(configuration, 'STATIC_ROOT', os.path.join(BASE_DIR, 'static')).rstrip('/')
STATIC_URL = f'/{BASE_PATH}static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'project-static', 'dist'),
+4 -1
View File
@@ -111,7 +111,7 @@ py.pkgs.buildPythonApplication rec {
pythonPath = py.pkgs.makePythonPath dependencies;
inherit (py.pkgs) gunicorn;
tests = {
netbox = nixosTests.netbox_4_3;
netbox = nixosTests.netbox_4_4;
inherit (nixosTests) netbox-upgrade;
};
updateScript = nix-update-script { };
@@ -123,6 +123,9 @@ py.pkgs.buildPythonApplication rec {
description = "IP address management (IPAM) and data center infrastructure management (DCIM) tool";
mainProgram = "netbox";
license = lib.licenses.asl20;
knownVulnerabilities = [
"Netbox Version ${version} is EOL; please upgrade by following the current release notes instructions"
];
maintainers = with lib.maintainers; [
minijackson
raitobezarius
@@ -16,14 +16,14 @@ let
in
py.pkgs.buildPythonApplication rec {
pname = "netbox";
version = "4.3.7";
version = "4.5.5";
pyproject = false;
src = fetchFromGitHub {
owner = "netbox-community";
repo = "netbox";
tag = "v${version}";
hash = "sha256-xVOP1D3C12M0M8oTrCA0M17NLuor+46UwiaKymSAVJM=";
hash = "sha256-he+WNbzIZSc2q97YjnAKHeFR0MDZCkDuAF/mfgAZuU4=";
};
patches = [
@@ -34,6 +34,7 @@ py.pkgs.buildPythonApplication rec {
(
with py.pkgs;
[
colorama
django
django-cors-headers
django-debug-toolbar
@@ -63,6 +64,7 @@ py.pkgs.buildPythonApplication rec {
requests
social-auth-core
social-auth-app-django
sorl-thumbnail
strawberry-graphql
strawberry-django
svgwrite
@@ -109,7 +111,7 @@ py.pkgs.buildPythonApplication rec {
pythonPath = py.pkgs.makePythonPath dependencies;
inherit (py.pkgs) gunicorn;
tests = {
netbox = nixosTests.netbox_4_3;
netbox = nixosTests.netbox_4_5;
inherit (nixosTests) netbox-upgrade;
};
updateScript = nix-update-script { };
@@ -121,9 +123,6 @@ py.pkgs.buildPythonApplication rec {
description = "IP address management (IPAM) and data center infrastructure management (DCIM) tool";
mainProgram = "netbox";
license = lib.licenses.asl20;
knownVulnerabilities = [
"Netbox Version ${version} is EOL; please upgrade by following the current release notes instructions"
];
maintainers = with lib.maintainers; [
minijackson
raitobezarius
@@ -34,14 +34,14 @@
buildPythonPackage rec {
pname = "strawberry-django";
version = "0.74.1";
version = "0.75.1";
pyproject = true;
src = fetchFromGitHub {
owner = "strawberry-graphql";
repo = "strawberry-django";
tag = version;
hash = "sha256-8T5lYM5JrxbeDCgvSGvBsimsF+tMwjS1hroqw2Gf7aA=";
hash = "sha256-vosD0JZYZzzl6Mp+AAxdXVox/7ay4FqnwqE6f1lSw3s=";
};
postPatch = ''
+1 -1
View File
@@ -2996,7 +2996,7 @@ with pkgs;
};
# Not in aliases because it wouldn't get picked up by callPackage
netbox = netbox_4_4;
netbox = netbox_4_5;
netcap-nodpi = callPackage ../by-name/ne/netcap/package.nix {
withDpi = false;