diff --git a/.devcontainer/docker-compose.override.yml b/.devcontainer/docker-compose.override.yml
index f8d64d2..31816f6 100644
--- a/.devcontainer/docker-compose.override.yml
+++ b/.devcontainer/docker-compose.override.yml
@@ -5,7 +5,7 @@ services:
dockerfile: Dockerfile-plugin_dev
context: .
ports:
- - 8000:8080
+ - "8000:8080"
volumes:
- ../:/opt/netbox/netbox/netbox-acls
- ~/.gitconfig:/home/vscode/.gitconfig:z,ro
diff --git a/.devcontainer/initializers/cables.yml b/.devcontainer/initializers/cables.yml
index b4a9137..9d100a5 100644
--- a/.devcontainer/initializers/cables.yml
+++ b/.devcontainer/initializers/cables.yml
@@ -3,7 +3,7 @@
# ```
# termination_x_name -> name of interface
# termination_x_device -> name of the device interface belongs to
-# termination_x_class -> required if different than 'Interface' which is the default
+# termination_x_class -> required if different from 'Interface' which is the default
# ```
#
# Supported termination classes: Interface, ConsolePort, ConsoleServerPort, FrontPort, RearPort, PowerPort, PowerOutlet
diff --git a/.gitignore b/.gitignore
index 7059dd0..841c0a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -161,3 +161,5 @@ cython_debug/
# VS Code
.vscode/
+# JetBrains
+.idea/
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9cf3305..cacec27 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,9 +3,9 @@
## Reporting Bugs
* First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases)
-of NetBox or this plugin [latest stable version](https://github.com/ryanmerolle/netbox-acls/releases).
+of NetBox or this plugin is at [latest stable version](https://github.com/ryanmerolle/netbox-acls/releases).
If you're running an older version, it's possible that the bug has already been fixed
-or you are running a version of the plugin not tested with the NetBox version
+or, you are running a version of the plugin not tested with the NetBox version
you are running [Compatibility Matrix](./README.md#compatibility).
* Next, check the GitHub [issues list](https://github.com/ryanmerolle/netbox-acls/issues)
diff --git a/README.md b/README.md
index 7668aab..cd51550 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ Based on the NetBox plugin tutorial by [jeremystretch](https://github.com/jeremy
- [demo repository](https://github.com/netbox-community/netbox-plugin-demo)
- [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial)
-All credit should go to Jeremy. Thanks Jeremy!
+All credit should go to Jeremy. Thanks, Jeremy!
This project just looks to build on top of this framework and model presented.
@@ -69,12 +69,12 @@ PLUGINS_CONFIG = {
To develop this plugin further one can use the included .devcontainer configuration. This configuration creates a docker container which includes a fully working netbox installation. Currently it should work when using WSL 2. For this to work make sure you have Docker Desktop installed and the WSL 2 integrations activated.
1. In the WSL terminal, enter `code` to run Visual studio code.
-1. Install the devcontainer extension "ms-vscode-remote.remote-containers"
-1. Press Ctrl+Shift+P and use the "Dev Container: Clone Repository in Container Volume" function to clone this repository. This will take a while depending on your computer
-1. If you'd like the netbox instance to be prepopulated run `make Makefile example_initializers` and `make Makefile load_initializers`
-1. Start the netbox instance using `make Makefile all`
+2. Install the devcontainer extension "ms-vscode-remote.remote-containers"
+3. Press Ctrl+Shift+P and use the "Dev Container: Clone Repository in Container Volume" function to clone this repository. This will take a while depending on your computer
+4. If you'd like the netbox instance to be prepopulated run `make Makefile example_initializers` and `make Makefile load_initializers`
+5. Start the netbox instance using `make Makefile all`
-Your netbox instance will be served under 0.0.0.0:8000 so it should now be available under localhost:8000.
+Your netbox instance will be served under 0.0.0.0:8000, so it should now be available under localhost:8000.
## Screenshots
diff --git a/netbox_acls/api/serializers.py b/netbox_acls/api/serializers.py
index e5dd7a2..b84a0bd 100644
--- a/netbox_acls/api/serializers.py
+++ b/netbox_acls/api/serializers.py
@@ -9,6 +9,7 @@ from drf_yasg.utils import swagger_serializer_method
from ipam.api.serializers import NestedPrefixSerializer
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer
+from netbox.constants import NESTED_SERIALIZER_PREFIX
from rest_framework import serializers
from utilities.api import get_serializer_for_model
@@ -82,7 +83,10 @@ class AccessListSerializer(NetBoxModelSerializer):
@swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_assigned_object(self, obj):
- serializer = get_serializer_for_model(obj.assigned_object, prefix="Nested")
+ serializer = get_serializer_for_model(
+ obj.assigned_object,
+ prefix=NESTED_SERIALIZER_PREFIX,
+ )
context = {"request": self.context["request"]}
return serializer(obj.assigned_object, context=context).data
@@ -96,6 +100,7 @@ class AccessListSerializer(NetBoxModelSerializer):
# Check that the GFK object is valid.
if "assigned_object_type" in data and "assigned_object_id" in data:
+ # TODO: This can removed after https://github.com/netbox-community/netbox/issues/10221 is fixed.
try:
assigned_object = data[ # noqa: F841
"assigned_object_type"
@@ -161,21 +166,25 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
@swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_assigned_object(self, obj):
- serializer = get_serializer_for_model(obj.assigned_object, prefix="Nested")
+ serializer = get_serializer_for_model(
+ obj.assigned_object,
+ prefix=NESTED_SERIALIZER_PREFIX,
+ )
context = {"request": self.context["request"]}
return serializer(obj.assigned_object, context=context).data
def validate(self, data):
"""
- Validate the AccessList django model model's inputs before allowing it to update the instance.
+ Validate the AccessList django model's inputs before allowing it to update the instance.
- Check that the GFK object is valid.
- Check that the associated interface's parent host has the selected ACL defined.
"""
error_message = {}
acl_host = data["access_list"].assigned_object
- # Check that the GFK object is vlaid.
+ # Check that the GFK object is valid.
if "assigned_object_type" in data and "assigned_object_id" in data:
+ # TODO: This can removed after https://github.com/netbox-community/netbox/issues/10221 is fixed.
try:
assigned_object = data[ # noqa: F841
"assigned_object_type"
@@ -200,6 +209,8 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
.get_object_for_this_type(id=data["assigned_object_id"])
.virtual_machine
)
+ else:
+ interface_host = None
# Check that the associated interface's parent host has the selected ACL defined.
if acl_host != interface_host:
error_acl_not_assigned_to_host = (
@@ -253,7 +264,7 @@ class ACLStandardRuleSerializer(NetBoxModelSerializer):
def validate(self, data):
"""
- Validate the ACLStandardRule django model model's inputs before allowing it to update the instance:
+ Validate the ACLStandardRule django model's inputs before allowing it to update the instance:
- Check if action set to remark, but no remark set.
- Check if action set to remark, but source_prefix set.
"""
@@ -322,7 +333,7 @@ class ACLExtendedRuleSerializer(NetBoxModelSerializer):
def validate(self, data):
"""
- Validate the ACLExtendedRule django model model's inputs before allowing it to update the instance:
+ Validate the ACLExtendedRule django model's inputs before allowing it to update the instance:
- Check if action set to remark, but no remark set.
- Check if action set to remark, but source_prefix set.
- Check if action set to remark, but source_ports set.
diff --git a/netbox_acls/choices.py b/netbox_acls/choices.py
index 8247d45..4b1c5b2 100644
--- a/netbox_acls/choices.py
+++ b/netbox_acls/choices.py
@@ -51,9 +51,12 @@ class ACLAssignmentDirectionChoices(ChoiceSet):
Defines the direction of the application of the ACL on an associated interface.
"""
+ DIRECTION_INGRESS = "ingress"
+ DIRECTION_EGRESS = "egress"
+
CHOICES = [
- ("ingress", "Ingress", "blue"),
- ("egress", "Egress", "purple"),
+ (DIRECTION_INGRESS, "Ingress", "blue"),
+ (DIRECTION_EGRESS, "Egress", "purple"),
]
@@ -62,9 +65,12 @@ class ACLTypeChoices(ChoiceSet):
Defines the choices availble for the Access Lists plugin specific to ACL type.
"""
+ TYPE_STANDARD = "standard"
+ TYPE_EXTENDED = "extended"
+
CHOICES = [
- ("extended", "Extended", "purple"),
- ("standard", "Standard", "blue"),
+ (TYPE_EXTENDED, "Extended", "purple"),
+ (TYPE_STANDARD, "Standard", "blue"),
]
@@ -73,8 +79,12 @@ class ACLProtocolChoices(ChoiceSet):
Defines the choices availble for the Access Lists plugin specific to ACL Rule protocol.
"""
+ PROTOCOL_ICMP = "icmp"
+ PROTOCOL_TCP = "tcp"
+ PROTOCOL_UDP = "udp"
+
CHOICES = [
- ("icmp", "ICMP", "purple"),
- ("tcp", "TCP", "blue"),
- ("udp", "UDP", "orange"),
+ (PROTOCOL_ICMP, "ICMP", "purple"),
+ (PROTOCOL_TCP, "TCP", "blue"),
+ (PROTOCOL_UDP, "UDP", "orange"),
]
diff --git a/netbox_acls/forms/models.py b/netbox_acls/forms/models.py
index 38f7c0c..c02640f 100644
--- a/netbox_acls/forms/models.py
+++ b/netbox_acls/forms/models.py
@@ -22,6 +22,7 @@ from virtualization.models import (
VMInterface,
)
+from ..choices import ACLTypeChoices
from ..models import (
AccessList,
ACLExtendedRule,
@@ -236,8 +237,12 @@ class AccessListForm(NetBoxModelForm):
"name": [error_same_acl_name],
}
# Check if Access List has no existing rules before change the Access List's type.
- if (acl_type == "extended" and self.instance.aclstandardrules.exists()) or (
- acl_type == "standard" and self.instance.aclextendedrules.exists()
+ if (
+ acl_type == ACLTypeChoices.TYPE_EXTENDED
+ and self.instance.aclstandardrules.exists()
+ ) or (
+ acl_type == ACLTypeChoices.TYPE_STANDARD
+ and self.instance.aclextendedrules.exists()
):
error_message["type"] = [
"This ACL has ACL rules associated, CANNOT change ACL type.",
@@ -312,10 +317,6 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
),
)
comments = CommentField()
- tags = DynamicModelMultipleChoiceField(
- queryset=Tag.objects.all(),
- required=False,
- )
def __init__(self, *args, **kwargs):
@@ -355,7 +356,7 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
"""
Validates form inputs before submitting:
- Check if both interface and vminterface are set.
- - Check if neither interface or vminterface are set.
+ - Check if neither interface nor vminterface are set.
- Check that an interface's parent device/virtual_machine is assigned to the Access List.
- Check that an interface's parent device/virtual_machine is assigned to the Access List.
- Check for duplicate entry. (Because of GFK)
@@ -367,7 +368,6 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
direction = cleaned_data.get("direction")
interface = cleaned_data.get("interface")
vminterface = cleaned_data.get("vminterface")
- assigned_object = cleaned_data.get("assigned_object")
# Check if both interface and vminterface are set.
if interface and vminterface:
@@ -403,40 +403,42 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
).pk
access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object
- # Check that an interface's parent device/virtual_machine is assigned to the Access List.
- if access_list_host != host:
- error_acl_not_assigned_to_host = "Access List not present on selected host."
- error_message |= {
- "access_list": [error_acl_not_assigned_to_host],
- assigned_object_type: [error_acl_not_assigned_to_host],
- host_type: [error_acl_not_assigned_to_host],
- }
- # Check for duplicate entry.
- if ACLInterfaceAssignment.objects.filter(
- access_list=access_list,
- assigned_object_id=assigned_object_id,
- assigned_object_type=assigned_object_type_id,
- direction=direction,
- ).exists():
- error_duplicate_entry = "An ACL with this name is already associated to this interface & direction."
- error_message |= {
- "access_list": [error_duplicate_entry],
- "direction": [error_duplicate_entry],
- assigned_object_type: [error_duplicate_entry],
- }
- # Check that the interface does not have an existing ACL applied in the direction already.
- if ACLInterfaceAssignment.objects.filter(
- assigned_object_id=assigned_object_id,
- assigned_object_type=assigned_object_type_id,
- direction=direction,
- ).exists():
- error_interface_already_assigned = (
- "Interfaces can only have 1 Access List assigned in each direction."
- )
- error_message |= {
- "direction": [error_interface_already_assigned],
- assigned_object_type: [error_interface_already_assigned],
- }
+ # Check that an interface's parent device/virtual_machine is assigned to the Access List.
+ if access_list_host != host:
+ error_acl_not_assigned_to_host = (
+ "Access List not present on selected host."
+ )
+ error_message |= {
+ "access_list": [error_acl_not_assigned_to_host],
+ assigned_object_type: [error_acl_not_assigned_to_host],
+ host_type: [error_acl_not_assigned_to_host],
+ }
+ # Check for duplicate entry.
+ if ACLInterfaceAssignment.objects.filter(
+ access_list=access_list,
+ assigned_object_id=assigned_object_id,
+ assigned_object_type=assigned_object_type_id,
+ direction=direction,
+ ).exists():
+ error_duplicate_entry = "An ACL with this name is already associated to this interface & direction."
+ error_message |= {
+ "access_list": [error_duplicate_entry],
+ "direction": [error_duplicate_entry],
+ assigned_object_type: [error_duplicate_entry],
+ }
+ # Check that the interface does not have an existing ACL applied in the direction already.
+ if ACLInterfaceAssignment.objects.filter(
+ assigned_object_id=assigned_object_id,
+ assigned_object_type=assigned_object_type_id,
+ direction=direction,
+ ).exists():
+ error_interface_already_assigned = (
+ "Interfaces can only have 1 Access List assigned in each direction."
+ )
+ error_message |= {
+ "direction": [error_interface_already_assigned],
+ assigned_object_type: [error_interface_already_assigned],
+ }
if error_message:
raise forms.ValidationError(error_message)
@@ -460,7 +462,7 @@ class ACLStandardRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField(
queryset=AccessList.objects.all(),
query_params={
- "type": "standard",
+ "type": ACLTypeChoices.TYPE_STANDARD,
},
help_text=mark_safe(
"*Note: This field will only display Standard ACLs.",
@@ -473,10 +475,6 @@ class ACLStandardRuleForm(NetBoxModelForm):
help_text=help_text_acl_rule_logic,
label="Source Prefix",
)
- tags = DynamicModelMultipleChoiceField(
- queryset=Tag.objects.all(),
- required=False,
- )
fieldsets = (
("Access List Details", ("access_list", "description", "tags")),
@@ -542,17 +540,14 @@ class ACLExtendedRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField(
queryset=AccessList.objects.all(),
query_params={
- "type": "extended",
+ "type": ACLTypeChoices.TYPE_EXTENDED,
},
help_text=mark_safe(
"*Note: This field will only display Extended ACLs.",
),
label="Access List",
)
- tags = DynamicModelMultipleChoiceField(
- queryset=Tag.objects.all(),
- required=False,
- )
+
source_prefix = DynamicModelChoiceField(
queryset=Prefix.objects.all(),
required=False,
diff --git a/netbox_acls/models/access_list_rules.py b/netbox_acls/models/access_list_rules.py
index f37aca8..5656720 100644
--- a/netbox_acls/models/access_list_rules.py
+++ b/netbox_acls/models/access_list_rules.py
@@ -8,7 +8,7 @@ from django.db import models
from django.urls import reverse
from netbox.models import NetBoxModel
-from ..choices import ACLProtocolChoices, ACLRuleActionChoices
+from ..choices import ACLProtocolChoices, ACLRuleActionChoices, ACLTypeChoices
from .access_lists import AccessList
__all__ = (
@@ -86,7 +86,7 @@ class ACLStandardRule(ACLRule):
on_delete=models.CASCADE,
to=AccessList,
verbose_name="Standard Access List",
- limit_choices_to={"type": "standard"},
+ limit_choices_to={"type": ACLTypeChoices.TYPE_STANDARD},
related_name="aclstandardrules",
)
diff --git a/netbox_acls/template_content.py b/netbox_acls/template_content.py
index 0de1844..598e71d 100644
--- a/netbox_acls/template_content.py
+++ b/netbox_acls/template_content.py
@@ -31,6 +31,9 @@ class ACLInterfaceAssignments(PluginTemplateExtension):
elif ctype.model == "vminterface":
parent_type = "virtual_machine"
parent_id = obj.virtual_machine.pk
+ else:
+ parent_type = None
+ parent_id = None
return self.render(
"inc/assigned_interface/access_lists.html",
diff --git a/netbox_acls/views.py b/netbox_acls/views.py
index df1eae8..19a0204 100644
--- a/netbox_acls/views.py
+++ b/netbox_acls/views.py
@@ -6,7 +6,7 @@ Specifically, all the various interactions with a client.
from django.db.models import Count
from netbox.views import generic
-from . import filtersets, forms, models, tables
+from . import choices, filtersets, forms, models, tables
__all__ = (
"AccessListView",
@@ -48,15 +48,22 @@ class AccessListView(generic.ObjectView):
"""
Depending on the Access List type, the list view will return the required ACL Rule using the previous defined tables in tables.py.
"""
- if instance.type == "extended":
- table = tables.ACLExtendedRuleTable(instance.aclextendedrules.all())
- elif instance.type == "standard":
- table = tables.ACLStandardRuleTable(instance.aclstandardrules.all())
- table.configure(request)
- return {
- "rules_table": table,
- }
+ if instance.type == choices.ACLTypeChoices.TYPE_EXTENDED:
+ table = tables.ACLExtendedRuleTable(instance.aclextendedrules.all())
+ elif instance.type == choices.ACLTypeChoices.TYPE_STANDARD:
+ table = tables.ACLStandardRuleTable(instance.aclstandardrules.all())
+ else:
+ table = None
+
+ if table:
+ table.columns.hide("access_list")
+ table.configure(request)
+
+ return {
+ "rules_table": table,
+ }
+ return {}
class AccessListListView(generic.ObjectListView):
@@ -84,7 +91,7 @@ class AccessListEditView(generic.ObjectEditView):
class AccessListDeleteView(generic.ObjectDeleteView):
"""
- Defines the delete view for the AccessLists django model.
+ Defines delete view for the AccessLists django model.
"""
queryset = models.AccessList.objects.all()
@@ -129,10 +136,20 @@ class ACLInterfaceAssignmentEditView(generic.ObjectEditView):
form = forms.ACLInterfaceAssignmentForm
template_name = "netbox_acls/aclinterfaceassignment_edit.html"
+ def get_extra_addanother_params(self, request):
+ """
+ Returns a dictionary of additional parameters to be passed to the "Add Another" button.
+ """
+
+ return {
+ "access_list": request.GET.get("access_list") or request.POST.get("access_list"),
+ "direction": request.GET.get("direction") or request.POST.get("direction"),
+ }
+
class ACLInterfaceAssignmentDeleteView(generic.ObjectDeleteView):
"""
- Defines the delete view for the ACLInterfaceAssignments django model.
+ Defines delete view for the ACLInterfaceAssignments django model.
"""
queryset = models.ACLInterfaceAssignment.objects.all()
@@ -176,10 +193,19 @@ class ACLStandardRuleEditView(generic.ObjectEditView):
queryset = models.ACLStandardRule.objects.all()
form = forms.ACLStandardRuleForm
+ def get_extra_addanother_params(self, request):
+ """
+ Returns a dictionary of additional parameters to be passed to the "Add Another" button.
+ """
+
+ return {
+ "access_list": request.GET.get("access_list") or request.POST.get("access_list"),
+ }
+
class ACLStandardRuleDeleteView(generic.ObjectDeleteView):
"""
- Defines the delete view for the ACLStandardRules django model.
+ Defines delete view for the ACLStandardRules django model.
"""
queryset = models.ACLStandardRule.objects.all()
@@ -223,10 +249,19 @@ class ACLExtendedRuleEditView(generic.ObjectEditView):
queryset = models.ACLExtendedRule.objects.all()
form = forms.ACLExtendedRuleForm
+ def get_extra_addanother_params(self, request):
+ """
+ Returns a dictionary of additional parameters to be passed to the "Add Another" button.
+ """
+
+ return {
+ "access_list": request.GET.get("access_list") or request.POST.get("access_list"),
+ }
+
class ACLExtendedRuleDeleteView(generic.ObjectDeleteView):
"""
- Defines the delete view for the ACLExtendedRules django model.
+ Defines delete view for the ACLExtendedRules django model.
"""
queryset = models.ACLExtendedRule.objects.all()