Merge pull request #85 from Onemind-Services-LLC/housekeeping/cleanup

This commit is contained in:
Ryan Merolle 2023-01-21 09:16:24 -05:00 committed by GitHub
commit 91e5d9c1c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 146 additions and 90 deletions

View File

@ -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

View File

@ -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

2
.gitignore vendored
View File

@ -161,3 +161,5 @@ cython_debug/
# VS Code
.vscode/
# JetBrains
.idea/

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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"),
]

View File

@ -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(
"<b>*Note:</b> 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(
"<b>*Note:</b> 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,

View File

@ -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",
)

View File

@ -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",

View File

@ -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()