diff --git a/docs/additional-features/webhooks.md b/docs/additional-features/webhooks.md index e3b352125..19133adb1 100644 --- a/docs/additional-features/webhooks.md +++ b/docs/additional-features/webhooks.md @@ -2,6 +2,9 @@ A webhook is a mechanism for conveying to some external system a change that took place in NetBox. For example, you may want to notify a monitoring system whenever the status of a device is updated in NetBox. This can be done by creating a webhook for the device model in NetBox and identifying the webhook receiver. When NetBox detects a change to a device, an HTTP request containing the details of the change and who made it be sent to the specified receiver. Webhooks are managed under Logging > Webhooks. +!!! warning + Webhooks support the inclusion of user-submitted code to generate custom headers and payloads, which may pose security risks under certain conditions. Only grant permission to create or modify webhooks to trusted users. + ## Configuration * **Name** - A unique name for the webhook. The name is not included with outbound messages. diff --git a/docs/customization/custom-links.md b/docs/customization/custom-links.md index dc8f28b71..44c8f403f 100644 --- a/docs/customization/custom-links.md +++ b/docs/customization/custom-links.md @@ -17,6 +17,9 @@ When viewing a device named Router4, this link would render as: Custom links appear as buttons in the top right corner of the page. Numeric weighting can be used to influence the ordering of links. +!!! warning + Custom links rely on user-created code to generate arbitrary HTML output, which may be dangerous. Only grant permission to create or modify custom links to trusted users. + ## Context Data The following context data is available within the template when rendering a custom link's text or URL. diff --git a/docs/customization/export-templates.md b/docs/customization/export-templates.md index 0d0f7169e..c6097c552 100644 --- a/docs/customization/export-templates.md +++ b/docs/customization/export-templates.md @@ -4,10 +4,13 @@ NetBox allows users to define custom templates that can be used when exporting o Each export template is associated with a certain type of object. For instance, if you create an export template for VLANs, your custom template will appear under the "Export" button on the VLANs list. Each export template must have a name, and may optionally designate a specific export [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) and/or file extension. +Export templates must be written in [Jinja2](https://jinja.palletsprojects.com/). + !!! note The name `table` is reserved for internal use. -Export templates must be written in [Jinja2](https://jinja.palletsprojects.com/). +!!! warning + Export templates are rendered using user-submitted code, which may pose security risks under certain conditions. Only grant permission to create or modify export templates to trusted users. The list of objects returned from the database when rendering an export template is stored in the `queryset` variable, which you'll typically want to iterate through using a `for` loop. Object properties can be access by name. For example: diff --git a/docs/installation/1-postgresql.md b/docs/installation/1-postgresql.md index 39008f188..644b2715c 100644 --- a/docs/installation/1-postgresql.md +++ b/docs/installation/1-postgresql.md @@ -11,13 +11,13 @@ This section entails the installation and configuration of a local PostgreSQL da ```no-highlight sudo apt update - sudo apt install -y postgresql libpq-dev + sudo apt install -y postgresql ``` === "CentOS" ```no-highlight - sudo yum install -y postgresql-server libpq-devel + sudo yum install -y postgresql-server sudo postgresql-setup --initdb ``` diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index 24d46006b..f0887aa4e 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -18,7 +18,7 @@ Begin by installing all system packages required by NetBox and its dependencies. === "CentOS" ```no-highlight - sudo yum install -y gcc python36 python36-devel python3-pip libxml2-devel libxslt-devel libffi-devel openssl-devel redhat-rpm-config + sudo yum install -y gcc python36 python36-devel python3-pip libxml2-devel libxslt-devel libffi-devel libpq-devel openssl-devel redhat-rpm-config ``` Before continuing with either platform, update pip (Python's package management tool) to its latest release: diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 162bbaf13..705472ac5 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -5,12 +5,18 @@ ### Enhancements * [#6883](https://github.com/netbox-community/netbox/issues/6883) - Add C21 & C22 power types +* [#6921](https://github.com/netbox-community/netbox/issues/6921) - Employ a sandbox when rendering Jinja2 code for increased security ### Bug Fixes * [#6740](https://github.com/netbox-community/netbox/issues/6740) - Add import button to VM interfaces list * [#6892](https://github.com/netbox-community/netbox/issues/6892) - Fix validation of unit ranges when creating a rack reservation * [#6902](https://github.com/netbox-community/netbox/issues/6902) - Populate device field when cloning device components +* [#6908](https://github.com/netbox-community/netbox/issues/6908) - Allow assignment of scope to VLAN groups upon import +* [#6909](https://github.com/netbox-community/netbox/issues/6909) - Remove extraneous `site` column from VLAN group import form +* [#6910](https://github.com/netbox-community/netbox/issues/6910) - Fix exception on invalid CSV import column name +* [#6935](https://github.com/netbox-community/netbox/issues/6935) - Remove extraneous columns from inventory item and device bay tables +* [#6936](https://github.com/netbox-community/netbox/issues/6936) - Add missing `parent` column to inventory item import form --- diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 50d8cbf75..74ac2ed84 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -4044,6 +4044,12 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm): to_field_name='name', required=False ) + parent = CSVModelChoiceField( + queryset=Device.objects.all(), + to_field_name='name', + required=False, + help_text='Parent inventory item' + ) class Meta: model = InventoryItem @@ -4051,6 +4057,21 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm): 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Limit parent choices to inventory items belonging to this device + device = None + if self.is_bound and 'device' in self.data: + try: + device = self.fields['device'].to_python(self.data['device']) + except forms.ValidationError: + pass + if device: + self.fields['parent'].queryset = InventoryItem.objects.filter(device=device) + else: + self.fields['parent'].queryset = InventoryItem.objects.none() + class InventoryItemBulkCreateForm( form_from_model(InventoryItem, ['manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered']), diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 8ac53aee6..432428003 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -242,10 +242,6 @@ class DeviceComponentTable(BaseTable): linkify=True, order_by=('_name',) ) - cable = tables.Column( - linkify=True - ) - mark_connected = BooleanColumn() class Meta(BaseTable.Meta): order_by = ('device', 'name') diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 15f5a9a9a..c38dc4ea7 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1741,10 +1741,10 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase): } cls.csv_data = ( - "device,name", - "Device 1,Inventory Item 4", - "Device 1,Inventory Item 5", - "Device 1,Inventory Item 6", + "device,name,parent", + "Device 1,Inventory Item 4,Inventory Item 1", + "Device 1,Inventory Item 5,Inventory Item 2", + "Device 1,Inventory Item 6,Inventory Item 3", ) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 4bb628b49..2205323e7 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -11,9 +11,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, ContentTypeChoiceField, CSVChoiceField, - CSVModelChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, - NumericArrayField, ReturnURLForm, SlugField, StaticSelect, StaticSelectMultiple, TagFilterField, - BOOLEAN_WITH_BLANK_CHOICES, + CSVContentTypeField, CSVModelChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, + ExpandableIPAddressField, NumericArrayField, ReturnURLForm, SlugField, StaticSelect, StaticSelectMultiple, + TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface from .choices import * @@ -1448,17 +1448,19 @@ class VLANGroupForm(BootstrapMixin, CustomFieldModelForm): class VLANGroupCSVForm(CustomFieldModelCSVForm): - site = CSVModelChoiceField( - queryset=Site.objects.all(), - required=False, - to_field_name='name', - help_text='Assigned site' - ) slug = SlugField() + scope_type = CSVContentTypeField( + queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES), + required=False, + label='Scope type (app & model)' + ) class Meta: model = VLANGroup fields = ('name', 'slug', 'scope_type', 'scope_id', 'description') + labels = { + 'scope_id': 'Scope ID', + } class VLANGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 471aa471d..2a0bfdf32 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -391,10 +391,10 @@ class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): } cls.csv_data = ( - "name,slug,description", - "VLAN Group 4,vlan-group-4,Fourth VLAN group", - "VLAN Group 5,vlan-group-5,Fifth VLAN group", - "VLAN Group 6,vlan-group-6,Sixth VLAN group", + f"name,slug,scope_type,scope_id,description", + f"VLAN Group 4,vlan-group-4,,,Fourth VLAN group", + f"VLAN Group 5,vlan-group-5,dcim.site,{sites[0].pk},Fifth VLAN group", + f"VLAN Group 6,vlan-group-6,dcim.site,{sites[1].pk},Sixth VLAN group", ) cls.bulk_edit_data = { diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index ebbb05891..8f8a00784 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -69,7 +69,7 @@ SECRET_KEY = '' # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of # application errors (assuming correct email settings are provided). ADMINS = [ - # ['John Doe', 'jdoe@example.com'], + # ('John Doe', 'jdoe@example.com'), ] # URL schemes that are allowed within links in NetBox diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py index d8ea04536..c63fe93e7 100644 --- a/netbox/netbox/views/generic.py +++ b/netbox/netbox/views/generic.py @@ -652,7 +652,7 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): ) def clean(self): - csv_rows = self.cleaned_data['csv'][1] + csv_rows = self.cleaned_data['csv'][1] if 'csv' in self.cleaned_data else None csv_file = self.files.get('csv_file') # Check that the user has not submitted both text data and a file diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 9b813dc76..3f8c6f84b 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -281,6 +281,8 @@ class CSVContentTypeField(CSVModelChoiceField): return f'{value.app_label}.{value.model}' def to_python(self, value): + if not value: + return None try: app_label, model = value.split('.') except ValueError: diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index bc8d5fbea..32880fca8 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -6,7 +6,7 @@ from itertools import count, groupby from django.core.serializers import serialize from django.db.models import Count, OuterRef, Subquery from django.db.models.functions import Coalesce -from jinja2 import Environment +from jinja2.sandbox import SandboxedEnvironment from mptt.models import MPTTModel from dcim.choices import CableLengthUnitChoices @@ -220,7 +220,7 @@ def render_jinja2(template_code, context): """ Render a Jinja2 template with the provided context. Return the rendered content. """ - return Environment().from_string(source=template_code).render(**context) + return SandboxedEnvironment().from_string(source=template_code).render(**context) def prepare_cloned_fields(instance):