From 917a2c261873b1fedcc3d59a45553a67abc10645 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Oct 2025 11:41:04 -0400 Subject: [PATCH] Validate peppers on init --- netbox/netbox/configuration_example.py | 10 ++++++++++ netbox/netbox/configuration_testing.py | 2 +- netbox/netbox/settings.py | 8 ++++---- netbox/utilities/security.py | 24 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 netbox/utilities/security.py diff --git a/netbox/netbox/configuration_example.py b/netbox/netbox/configuration_example.py index 612f75a409..18d30d29a5 100644 --- a/netbox/netbox/configuration_example.py +++ b/netbox/netbox/configuration_example.py @@ -68,6 +68,16 @@ REDIS = { # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY SECRET_KEY = '' +# Define a mapping of cryptographic peppers to use when hashing API tokens. A minimum of one pepper is required to +# enable v2 API tokens (NetBox v4.5+). Define peppers as a mapping of numeric ID to pepper value, as shown below. Each +# pepper must be at least 50 characters in length. +# +# API_TOKEN_PEPPERS = { +# 1: "", +# 2: "", +# } +API_TOKEN_PEPPERS = {} + ######################### # # diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index 36f9d7338e..6d1de20087 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -46,7 +46,7 @@ DEFAULT_PERMISSIONS = {} ALLOW_TOKEN_RETRIEVAL = True API_TOKEN_PEPPERS = { - 1: 'TEST-VALUE-DO-NOT-USE', + 1: 'TEST-VALUE-DO-NOT-USE-TEST-VALUE-DO-NOT-USE-TEST-VALUE-DO-NOT-USE', } LOGGING = { diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index a912c2d6e8..828f731090 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -19,6 +19,7 @@ from netbox.plugins import PluginConfig from netbox.registry import registry import storages.utils # type: ignore from utilities.release import load_release_data +from utilities.security import validate_peppers from utilities.string import trailing_slash # @@ -217,10 +218,9 @@ if len(SECRET_KEY) < 50: ) # Validate API token peppers -for key in API_TOKEN_PEPPERS: - if type(key) is not int: - raise ImproperlyConfigured(f"Invalid API_TOKEN_PEPPERS key: {key}. All keys must be integers.") -if not API_TOKEN_PEPPERS: +if API_TOKEN_PEPPERS: + validate_peppers(API_TOKEN_PEPPERS) +else: warnings.warn("API_TOKEN_PEPPERS is not defined. v2 API tokens cannot be used.") # Validate update repo URL and timeout diff --git a/netbox/utilities/security.py b/netbox/utilities/security.py new file mode 100644 index 0000000000..47a18d2653 --- /dev/null +++ b/netbox/utilities/security.py @@ -0,0 +1,24 @@ +from django.core.exceptions import ImproperlyConfigured + +__all__ = ( + 'validate_peppers', +) + + +def validate_peppers(peppers): + """ + Validate the given dictionary of cryptographic peppers for type & sufficient length. + """ + if type(peppers) is not dict: + raise ImproperlyConfigured("API_TOKEN_PEPPERS must be a dictionary.") + for key, pepper in peppers.items(): + if type(key) is not int: + raise ImproperlyConfigured(f"Invalid API_TOKEN_PEPPERS key: {key}. All keys must be integers.") + if not 0 <= key <= 32767: + raise ImproperlyConfigured( + f"Invalid API_TOKEN_PEPPERS key: {key}. Key values must be between 0 and 32767, inclusive." + ) + if type(pepper) is not str: + raise ImproperlyConfigured(f"Invalid pepper {key}: Pepper value must be a string.") + if len(pepper) < 50: + raise ImproperlyConfigured(f"Invalid pepper {key}: Pepper must be at least 50 characters in length.")