mirror of
https://github.com/lucaspalomodevelop/netbox-acls.git
synced 2026-03-12 23:27:23 +00:00
Allow ACL associations to interfaces (#43)
This commit is contained in:
parent
b0288037a2
commit
63a5188862
@ -1,6 +1,6 @@
|
||||
ARG VARIANT=latest
|
||||
|
||||
FROM tgenannt/netbox:${VARIANT}
|
||||
FROM netboxcommunity/netbox:${VARIANT}
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
version: '3.4'
|
||||
services:
|
||||
netbox: &netbox
|
||||
image: tgenannt/netbox:${VARIANT-latest}
|
||||
image: netboxcommunity/netbox:${VARIANT-latest}
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
@ -13,15 +13,15 @@ services:
|
||||
- ./initializers:/opt/netbox/initializers:z,ro
|
||||
- ./configuration:/etc/netbox/config:z,ro
|
||||
#- netbox-media-files:/opt/netbox/netbox/media:z
|
||||
netbox-worker:
|
||||
<<: *netbox
|
||||
depends_on:
|
||||
- redis
|
||||
- postgres
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- /opt/netbox/netbox/manage.py
|
||||
- rqworker
|
||||
#netbox-worker:
|
||||
# <<: *netbox
|
||||
# depends_on:
|
||||
# - redis
|
||||
# - postgres
|
||||
# command:
|
||||
# - /opt/netbox/venv/bin/python
|
||||
# - /opt/netbox/netbox/manage.py
|
||||
# - rqworker
|
||||
#netbox-housekeeping:
|
||||
# <<: *netbox
|
||||
# depends_on:
|
||||
|
||||
4
.devcontainer/env/netbox.env
vendored
4
.devcontainer/env/netbox.env
vendored
@ -1,10 +1,12 @@
|
||||
ALLOWED_HOSTS=*
|
||||
CORS_ORIGIN_ALLOW_ALL=True
|
||||
CORS_ORIGIN_ALLOW_ALL=true
|
||||
DB_HOST=postgres
|
||||
DB_NAME=netbox
|
||||
DB_PASSWORD=J5brHrAXFLQSif0K
|
||||
DB_USER=netbox
|
||||
DEBUG=true
|
||||
ENFORCE_GLOBAL_UNIQUE=true
|
||||
LOGIN_REQUIRED=false
|
||||
GRAPHQL_ENABLED=true
|
||||
MAX_PAGE_SIZE=1000
|
||||
MEDIA_ROOT=/opt/netbox/netbox/media
|
||||
|
||||
5
Makefile
5
Makefile
@ -24,6 +24,11 @@ cleanup: ## Clean associated docker resources.
|
||||
|
||||
# in VS Code Devcontianer
|
||||
|
||||
.PHONY: nbshell
|
||||
nbshell: ## Run nbshell
|
||||
${VENV_PY_PATH} ${NETBOX_MANAGE_PATH} nbshell
|
||||
from netbox_access_lists.models import *
|
||||
|
||||
.PHONY: setup
|
||||
setup: ## Copy plugin settings. Setup NetBox plugin.
|
||||
-${VENV_PY_PATH} -m pip install --disable-pip-version-check --no-cache-dir -e ${REPO_PATH}
|
||||
|
||||
@ -6,10 +6,12 @@ while Django itself handles the database abstraction.
|
||||
from netbox.api.serializers import WritableNestedSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
from ..models import (AccessList, ACLExtendedRule, ACLInterfaceAssignment,
|
||||
ACLStandardRule)
|
||||
|
||||
__all__ = [
|
||||
'NestedAccessListSerializer',
|
||||
'NestedACLInterfaceAssignmentSerializer',
|
||||
'NestedACLStandardRuleSerializer',
|
||||
'NestedACLExtendedRuleSerializer'
|
||||
]
|
||||
@ -30,6 +32,22 @@ class NestedAccessListSerializer(WritableNestedSerializer):
|
||||
fields = ('id', 'url', 'display', 'name')
|
||||
|
||||
|
||||
class NestedACLInterfaceAssignmentSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
Defines the nested serializer for the django ACLInterfaceAssignment model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:aclinterfaceassignment-detail'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLInterfaceAssignment & fields to the nested serializer.
|
||||
"""
|
||||
model = ACLInterfaceAssignment
|
||||
fields = ('id', 'url', 'display', 'access_list')
|
||||
|
||||
|
||||
class NestedACLStandardRuleSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
Defines the nested serializer for the django ACLStandardRule model & associates it to a view.
|
||||
|
||||
@ -6,23 +6,37 @@ while Django itself handles the database abstraction.
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from drf_yasg.utils import swagger_serializer_method
|
||||
from ipam.api.serializers import NestedPrefixSerializer
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from netbox.api import ContentTypeField
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from rest_framework import serializers
|
||||
from utilities.api import get_serializer_for_model
|
||||
|
||||
from ..constants import ACL_HOST_ASSIGNMENT_MODELS
|
||||
from ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
from ..constants import (ACL_HOST_ASSIGNMENT_MODELS,
|
||||
ACL_INTERFACE_ASSIGNMENT_MODELS)
|
||||
from ..models import (AccessList, ACLExtendedRule, ACLInterfaceAssignment,
|
||||
ACLStandardRule)
|
||||
from .nested_serializers import (NestedAccessListSerializer,
|
||||
NestedACLExtendedRuleSerializer,
|
||||
NestedACLInterfaceAssignmentSerializer,
|
||||
NestedACLStandardRuleSerializer)
|
||||
|
||||
__all__ = [
|
||||
'NestedAccessListSerializer',
|
||||
'NestedACLStandardRuleSerializer',
|
||||
'NestedACLExtendedRuleSerializer'
|
||||
'AccessListSerializer',
|
||||
'ACLInterfaceAssignmentSerializer',
|
||||
'ACLStandardRuleSerializer',
|
||||
'ACLExtendedRuleSerializer'
|
||||
]
|
||||
|
||||
# Sets a standard error message for ACL rules with an action of remark, but no remark set.
|
||||
error_message_no_remark = 'Action is set to remark, you MUST add a remark.'
|
||||
# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set.
|
||||
error_message_action_remark_source_prefix_set = 'Action is set to remark, Source Prefix CANNOT be set.'
|
||||
# Sets a standard error message for ACL rules with an action not set to remark, but no remark is set.
|
||||
error_message_remark_without_action_remark = 'CANNOT set remark unless action is set to remark.'
|
||||
# Sets a standard error message for ACL rules no associated to an ACL of the same type.
|
||||
error_message_acl_type = 'Provided parent Access List is not of right type.'
|
||||
|
||||
|
||||
class AccessListSerializer(NetBoxModelSerializer):
|
||||
"""
|
||||
@ -55,16 +69,95 @@ class AccessListSerializer(NetBoxModelSerializer):
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validate the AccessList django model model's inputs before allowing it to update the instance.
|
||||
Validates api inputs before processing:
|
||||
- Check that the GFK object is valid.
|
||||
- Check if Access List has no existing rules before change the Access List's type.
|
||||
"""
|
||||
if self.instance.rule_count > 0:
|
||||
raise serializers.ValidationError({
|
||||
'type': 'This ACL has ACL rules already associated, CANNOT change ACL type!!'
|
||||
})
|
||||
error_message = {}
|
||||
|
||||
# Check that the GFK object is valid.
|
||||
if 'assigned_object_type' in data and 'assigned_object_id' in data:
|
||||
try:
|
||||
assigned_object = data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
|
||||
except ObjectDoesNotExist:
|
||||
# Sets a standard error message for invalid GFK
|
||||
error_message_invalid_gfk = f"Invalid assigned_object {data['assigned_object_type']} ID {data['assigned_object_id']}"
|
||||
error_message['assigned_object_type'] = [error_message_invalid_gfk]
|
||||
error_message['assigned_object_id'] = [error_message_invalid_gfk]
|
||||
|
||||
# Check if Access List has no existing rules before change the Access List's type.
|
||||
if self.instance and self.instance.type != data.get('type') and self.instance.rule_count > 0:
|
||||
error_message['type'] = ['This ACL has ACL rules associated, CANNOT change ACL type.']
|
||||
|
||||
if error_message:
|
||||
raise serializers.ValidationError(error_message)
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
|
||||
"""
|
||||
Defines the serializer for the django ACLInterfaceAssignment model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:aclinterfaceassignment-detail'
|
||||
)
|
||||
assigned_object_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(ACL_INTERFACE_ASSIGNMENT_MODELS)
|
||||
)
|
||||
assigned_object = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLInterfaceAssignment & fields to the serializer.
|
||||
"""
|
||||
model = ACLInterfaceAssignment
|
||||
fields = (
|
||||
'id', 'url', 'access_list', 'direction', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated'
|
||||
)
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
||||
def get_assigned_object(self, obj):
|
||||
serializer = get_serializer_for_model(obj.assigned_object, prefix='Nested')
|
||||
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.
|
||||
- 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.
|
||||
if 'assigned_object_type' in data and 'assigned_object_id' in data:
|
||||
try:
|
||||
assigned_object = data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
|
||||
except ObjectDoesNotExist:
|
||||
# Sets a standard error message for invalid GFK
|
||||
error_message_invalid_gfk = f"Invalid assigned_object {data['assigned_object_type']} ID {data['assigned_object_id']}"
|
||||
error_message['assigned_object_type'] = [error_message_invalid_gfk]
|
||||
error_message['assigned_object_id'] = [error_message_invalid_gfk]
|
||||
|
||||
|
||||
if data['assigned_object_type'].model == 'interface':
|
||||
interface_host = data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id']).device
|
||||
elif data['assigned_object_type'].model == 'vminterface':
|
||||
interface_host = data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id']).virtual_machine
|
||||
# Check that the associated interface's parent host has the selected ACL defined.
|
||||
if acl_host != interface_host:
|
||||
error_acl_not_assigned_to_host = "Access List not present on the selected interface's host."
|
||||
error_message['access_list'] = [error_acl_not_assigned_to_host]
|
||||
error_message['assigned_object_id'] = [error_acl_not_assigned_to_host]
|
||||
|
||||
if error_message:
|
||||
raise serializers.ValidationError(error_message)
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
class ACLStandardRuleSerializer(NetBoxModelSerializer):
|
||||
"""
|
||||
Defines the serializer for the django ACLStandardRule model & associates it to a view.
|
||||
@ -73,7 +166,11 @@ class ACLStandardRuleSerializer(NetBoxModelSerializer):
|
||||
view_name='plugins-api:netbox_access_lists-api:aclstandardrule-detail'
|
||||
)
|
||||
access_list = NestedAccessListSerializer()
|
||||
source_prefix = NestedPrefixSerializer()
|
||||
source_prefix = NestedPrefixSerializer(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
@ -82,18 +179,26 @@ class ACLStandardRuleSerializer(NetBoxModelSerializer):
|
||||
model = ACLStandardRule
|
||||
fields = (
|
||||
'id', 'url', 'display', 'access_list', 'index', 'action', 'tags', 'description',
|
||||
'created', 'custom_fields', 'last_updated', 'source_prefix'
|
||||
'remark', 'created', 'custom_fields', 'last_updated', 'source_prefix'
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validate the ACLStandardRule django model model's inputs before allowing it to update the instance.
|
||||
Validate the ACLStandardRule django model 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.
|
||||
"""
|
||||
access_list = data.get('access_list')
|
||||
if access_list.type == 'extended':
|
||||
raise serializers.ValidationError({
|
||||
'access_list': 'CANNOT associated standard ACL rules to an extended ACL!!'
|
||||
})
|
||||
error_message = {}
|
||||
|
||||
# Check if action set to remark, but no remark set.
|
||||
if data.get('action') == 'remark' and data.get('remark') is None:
|
||||
error_message['remark'] = [error_message_no_remark]
|
||||
# Check if action set to remark, but source_prefix set.
|
||||
if data.get('source_prefix'):
|
||||
error_message['source_prefix'] = [error_message_action_remark_source_prefix_set]
|
||||
|
||||
if error_message:
|
||||
raise serializers.ValidationError(error_message)
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
@ -106,8 +211,16 @@ class ACLExtendedRuleSerializer(NetBoxModelSerializer):
|
||||
view_name='plugins-api:netbox_access_lists-api:aclextendedrule-detail'
|
||||
)
|
||||
access_list = NestedAccessListSerializer()
|
||||
source_prefix = NestedPrefixSerializer()
|
||||
destination_prefix = NestedPrefixSerializer()
|
||||
source_prefix = NestedPrefixSerializer(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
destination_prefix = NestedPrefixSerializer(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
@ -117,17 +230,42 @@ class ACLExtendedRuleSerializer(NetBoxModelSerializer):
|
||||
fields = (
|
||||
'id', 'url', 'display', 'access_list', 'index', 'action', 'tags', 'description',
|
||||
'created', 'custom_fields', 'last_updated', 'source_prefix', 'source_ports',
|
||||
'destination_prefix', 'destination_ports', 'protocol'
|
||||
'destination_prefix', 'destination_ports', 'protocol', 'remark',
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validate the ACLExtendedRule django model model's inputs before allowing it to update the instance.
|
||||
Validate the ACLExtendedRule django model 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.
|
||||
- Check if action set to remark, but destination_prefix set.
|
||||
- Check if action set to remark, but destination_ports set.
|
||||
- Check if action set to remark, but protocol set.
|
||||
- Check if action set to remark, but protocol set.
|
||||
"""
|
||||
access_list = data.get('access_list')
|
||||
if access_list.type == 'standard':
|
||||
raise serializers.ValidationError({
|
||||
'access_list': 'CANNOT associated extended ACL rules to a standard ACL!!'
|
||||
})
|
||||
error_message = {}
|
||||
|
||||
# Check if action set to remark, but no remark set.
|
||||
if data.get('action') == 'remark' and data.get('remark') is None:
|
||||
error_message['remark'] = [error_message_no_remark]
|
||||
# Check if action set to remark, but source_prefix set.
|
||||
if data.get('source_prefix'):
|
||||
error_message['source_prefix'] = [error_message_action_remark_source_prefix_set]
|
||||
# Check if action set to remark, but source_ports set.
|
||||
if data.get('source_ports'):
|
||||
error_message['source_ports'] = ['Action is set to remark, Source Ports CANNOT be set.']
|
||||
# Check if action set to remark, but destination_prefix set.
|
||||
if data.get('destination_prefix'):
|
||||
error_message['destination_prefix'] = ['Action is set to remark, Destination Prefix CANNOT be set.']
|
||||
# Check if action set to remark, but destination_ports set.
|
||||
if data.get('destination_ports'):
|
||||
error_message['destination_ports'] = ['Action is set to remark, Destination Ports CANNOT be set.']
|
||||
# Check if action set to remark, but protocol set.
|
||||
if data.get('protocol'):
|
||||
error_message['protocol'] = ['Action is set to remark, Protocol CANNOT be set.']
|
||||
|
||||
if error_message:
|
||||
raise serializers.ValidationError(error_message)
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
@ -10,6 +10,7 @@ app_name = 'netbox_access_list'
|
||||
|
||||
router = NetBoxRouter()
|
||||
router.register('access-lists', views.AccessListViewSet)
|
||||
router.register('interface-assignments', views.ACLInterfaceAssignmentViewSet)
|
||||
router.register('standard-acl-rules', views.ACLStandardRuleViewSet)
|
||||
router.register('extended-acl-rules', views.ACLExtendedRuleViewSet)
|
||||
|
||||
|
||||
@ -9,11 +9,13 @@ from netbox.api.viewsets import NetBoxModelViewSet
|
||||
|
||||
from .. import filtersets, models
|
||||
from .serializers import (AccessListSerializer, ACLExtendedRuleSerializer,
|
||||
ACLInterfaceAssignmentSerializer,
|
||||
ACLStandardRuleSerializer)
|
||||
|
||||
__all__ = [
|
||||
'AccessListViewSet',
|
||||
'ACLStandardRuleViewSet',
|
||||
'ACLInterfaceAssignmentViewSet'
|
||||
'ACLExtendedRuleViewSet',
|
||||
]
|
||||
|
||||
@ -29,6 +31,15 @@ class AccessListViewSet(NetBoxModelViewSet):
|
||||
filterset_class = filtersets.AccessListFilterSet
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentViewSet(NetBoxModelViewSet):
|
||||
"""
|
||||
Defines the view set for the django ACLInterfaceAssignment model & associates it to a view.
|
||||
"""
|
||||
queryset = models.ACLInterfaceAssignment.objects.prefetch_related('access_list', 'tags')
|
||||
serializer_class = ACLInterfaceAssignmentSerializer
|
||||
filterset_class = filtersets.ACLInterfaceAssignmentFilterSet
|
||||
|
||||
|
||||
class ACLStandardRuleViewSet(NetBoxModelViewSet):
|
||||
"""
|
||||
Defines the view set for the django ACLStandardRule model & associates it to a view.
|
||||
|
||||
@ -6,6 +6,8 @@ from utilities.choices import ChoiceSet
|
||||
|
||||
__all__ = (
|
||||
'ACLActionChoices',
|
||||
'ACLAssignmentDirectionChoices',
|
||||
'ACLProtocolChoices',
|
||||
'ACLRuleActionChoices',
|
||||
'ACLTypeChoices',
|
||||
'ACLProtocolChoices',
|
||||
@ -42,6 +44,17 @@ class ACLRuleActionChoices(ChoiceSet):
|
||||
]
|
||||
|
||||
|
||||
class ACLAssignmentDirectionChoices(ChoiceSet):
|
||||
"""
|
||||
Defines the direction of the application of the ACL on an associated interface.
|
||||
"""
|
||||
|
||||
CHOICES = [
|
||||
('ingress', 'Ingress', 'blue'),
|
||||
('egress', 'Egress', 'purple'),
|
||||
]
|
||||
|
||||
|
||||
class ACLTypeChoices(ChoiceSet):
|
||||
"""
|
||||
Defines the choices availble for the Access Lists plugin specific to ACL type.
|
||||
|
||||
@ -3,15 +3,16 @@ Filters enable users to request only a specific subset of objects matching a que
|
||||
when filtering the sites list by status or region, for instance.
|
||||
"""
|
||||
import django_filters
|
||||
from dcim.models import Device, VirtualChassis
|
||||
from dcim.models import Device, Interface, VirtualChassis
|
||||
from netbox.filtersets import NetBoxModelFilterSet
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
|
||||
from .models import *
|
||||
|
||||
__all__ = (
|
||||
'AccessListFilterSet',
|
||||
'ACLStandardRuleFilterSet',
|
||||
'ACLInterfaceAssignmentFilterSet',
|
||||
'ACLExtendedRuleFilterSet',
|
||||
)
|
||||
|
||||
@ -68,6 +69,47 @@ class AccessListFilterSet(NetBoxModelFilterSet):
|
||||
return queryset.filter(description__icontains=value)
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentFilterSet(NetBoxModelFilterSet):
|
||||
"""
|
||||
Define the filter set for the django model ACLInterfaceAssignment.
|
||||
"""
|
||||
interface = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='interface__name',
|
||||
queryset=Interface.objects.all(),
|
||||
to_field_name='name',
|
||||
label='Interface (name)',
|
||||
)
|
||||
interface_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='interface',
|
||||
queryset=Interface.objects.all(),
|
||||
label='Interface (ID)',
|
||||
)
|
||||
vminterface = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='vminterface__name',
|
||||
queryset=VMInterface.objects.all(),
|
||||
to_field_name='name',
|
||||
label='VM Interface (name)',
|
||||
)
|
||||
vminterface_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='vminterface',
|
||||
queryset=VMInterface.objects.all(),
|
||||
label='VM Interface (ID)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLInterfaceAssignment & fields to the filter set.
|
||||
"""
|
||||
model = ACLInterfaceAssignment
|
||||
fields = ('id', 'access_list', 'direction', 'interface', 'interface_id', 'vminterface', 'vminterface_id')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
"""
|
||||
Override the default search behavior for the django model.
|
||||
"""
|
||||
return queryset.filter(description__icontains=value)
|
||||
|
||||
|
||||
class ACLStandardRuleFilterSet(NetBoxModelFilterSet):
|
||||
"""
|
||||
Define the filter set for the django model ACLStandardRule.
|
||||
|
||||
@ -77,7 +77,7 @@ from ..models import AccessList
|
||||
# return cleaned_data
|
||||
# name = cleaned_data.get('name')
|
||||
# device = cleaned_data.get('device')
|
||||
# type = cleaned_data.get('type')
|
||||
# type = cleaned_data.get('type')
|
||||
# if ('name' in self.changed_data or 'device' in self.changed_data) and AccessList.objects.filter(name__iexact=name, device=device).exists():
|
||||
# raise forms.ValidationError('An ACL with this name (case insensitive) is already associated to this device.')
|
||||
# if type == 'extended' and self.cleaned_data['aclstandardrules'].exists():
|
||||
|
||||
@ -2,21 +2,25 @@
|
||||
Defines each django model's GUI filter/search options.
|
||||
"""
|
||||
|
||||
from dcim.models import Device, Region, Site, SiteGroup, VirtualChassis
|
||||
from dcim.models import (Device, Interface, Region, Site, SiteGroup,
|
||||
VirtualChassis)
|
||||
from django import forms
|
||||
from ipam.models import Prefix
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from utilities.forms import (ChoiceField, DynamicModelChoiceField,
|
||||
StaticSelect, StaticSelectMultiple,
|
||||
TagFilterField, add_blank_choice)
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
|
||||
from ..choices import (ACLActionChoices, ACLProtocolChoices,
|
||||
ACLRuleActionChoices, ACLTypeChoices)
|
||||
from ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
from ..choices import (ACLActionChoices, ACLAssignmentDirectionChoices,
|
||||
ACLProtocolChoices, ACLRuleActionChoices,
|
||||
ACLTypeChoices)
|
||||
from ..models import (AccessList, ACLExtendedRule, ACLInterfaceAssignment,
|
||||
ACLStandardRule)
|
||||
|
||||
__all__ = (
|
||||
'AccessListFilterForm',
|
||||
'ACLInterfaceAssignmentFilterForm',
|
||||
'ACLStandardRuleFilterForm',
|
||||
'ACLExtendedRuleFilterForm',
|
||||
)
|
||||
@ -71,6 +75,75 @@ class AccessListFilterForm(NetBoxModelFilterSetForm):
|
||||
)
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentFilterForm(NetBoxModelFilterSetForm):
|
||||
"""
|
||||
GUI filter form to search the django AccessList model.
|
||||
"""
|
||||
model = ACLInterfaceAssignment
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
label='Site Group'
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False
|
||||
)
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
query_params={
|
||||
'region': '$region',
|
||||
'group_id': '$site_group',
|
||||
'site_id': '$site',
|
||||
},
|
||||
required=False
|
||||
)
|
||||
interface = DynamicModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
label='Virtual Machine',
|
||||
)
|
||||
vminterface = DynamicModelChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'virtual_machine_id': '$virtual_machine'
|
||||
},
|
||||
label='Interface'
|
||||
)
|
||||
access_list = DynamicModelChoiceField(
|
||||
queryset=AccessList.objects.all(),
|
||||
query_params={
|
||||
'assigned_object': '$device',
|
||||
},
|
||||
label='Access List',
|
||||
)
|
||||
direction = ChoiceField(
|
||||
choices=add_blank_choice(ACLAssignmentDirectionChoices),
|
||||
required=False,
|
||||
initial='',
|
||||
widget=StaticSelect(),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
#fieldsets = (
|
||||
# (None, ('q', 'tag')),
|
||||
# ('Host Details', ('region', 'site_group', 'site', 'device')),
|
||||
# ('ACL Details', ('type', 'default_action')),
|
||||
#)
|
||||
|
||||
|
||||
class ACLStandardRuleFilterForm(NetBoxModelFilterSetForm):
|
||||
"""
|
||||
GUI filter form to search the django ACLStandardRule model.
|
||||
|
||||
@ -2,26 +2,41 @@
|
||||
Defines each django model's GUI form to add or edit objects for each django model.
|
||||
"""
|
||||
|
||||
from dcim.models import Device, Region, Site, SiteGroup, VirtualChassis
|
||||
from dcim.models import (Device, Interface, Region, Site, SiteGroup,
|
||||
VirtualChassis)
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import mark_safe
|
||||
from extras.models import Tag
|
||||
from ipam.models import Prefix
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms import (CommentField, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField)
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
|
||||
from ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
from ..models import (AccessList, ACLExtendedRule, ACLInterfaceAssignment,
|
||||
ACLStandardRule)
|
||||
|
||||
__all__ = (
|
||||
'AccessListForm',
|
||||
'ACLInterfaceAssignmentForm',
|
||||
'ACLStandardRuleForm',
|
||||
'ACLExtendedRuleForm',
|
||||
)
|
||||
|
||||
# Sets a standard mark_save help_text value to be used by the various classes
|
||||
acl_rule_logic_help = mark_safe('<b>*Note:</b> CANNOT be set if action is set to remark.')
|
||||
# Sets a standard mark_safe help_text value to be used by the various classes
|
||||
help_text_acl_rule_logic = mark_safe('<b>*Note:</b> CANNOT be set if action is set to remark.')
|
||||
# Sets a standard help_text value to be used by the various classes for acl action
|
||||
help_text_acl_action = 'Action the rule will take (remark, deny, or allow).'
|
||||
# Sets a standard help_text value to be used by the various classes for acl index
|
||||
help_text_acl_rule_index = 'Determines the order of the rule in the ACL processing. AKA Sequence Number.'
|
||||
|
||||
# Sets a standard error message for ACL rules with an action of remark, but no remark set.
|
||||
error_message_no_remark = 'Action is set to remark, you MUST add a remark.'
|
||||
# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set.
|
||||
error_message_action_remark_source_prefix_set = 'Action is set to remark, Source Prefix CANNOT be set.'
|
||||
# Sets a standard error message for ACL rules with an action not set to remark, but no remark is set.
|
||||
error_message_remark_without_action_remark = 'CANNOT set remark unless action is set to remark.'
|
||||
|
||||
|
||||
class AccessListForm(NetBoxModelForm):
|
||||
@ -94,47 +109,50 @@ class AccessListForm(NetBoxModelForm):
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Validates form inputs before submitting.
|
||||
Validates form inputs before submitting:
|
||||
- Check if more than one host type selected.
|
||||
- Check if no hosts selected.
|
||||
- Check if duplicate entry. (Because of GFK.)
|
||||
- Check if Access List has no existing rules before change the Access List's type.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
if self.errors.get('name'):
|
||||
return cleaned_data
|
||||
name = cleaned_data.get('name')
|
||||
type = cleaned_data.get('type')
|
||||
acl_type = cleaned_data.get('type')
|
||||
device = cleaned_data.get('device')
|
||||
virtual_chassis = cleaned_data.get('virtual_chassis')
|
||||
virtual_machine = cleaned_data.get('virtual_machine')
|
||||
|
||||
|
||||
# Check if more than one host type selected.
|
||||
if (device and virtual_chassis) or (device and virtual_machine) or (virtual_chassis and virtual_machine):
|
||||
raise forms.ValidationError('Access Lists must be assigned to one host (either a device, virtual chassis or virtual machine).')
|
||||
raise forms.ValidationError('Access Lists must be assigned to one host (either a device, virtual chassis or virtual machine) at a time.')
|
||||
# Check if no hosts selected.
|
||||
if not device and not virtual_chassis and not virtual_machine:
|
||||
raise forms.ValidationError('Access Lists must be assigned to a device, virtual chassis or virtual machine.')
|
||||
if ('name' in self.changed_data or 'device' in self.changed_data) and device and AccessList.objects.filter(name__iexact=name, device=device).exists():
|
||||
error_message.update(
|
||||
{
|
||||
'device': ['An ACL with this name (case insensitive) is already associated to this host.'],
|
||||
'name': ['An ACL with this name (case insensitive) is already associated to this host.'],
|
||||
}
|
||||
)
|
||||
if ('name' in self.changed_data or 'virtual_chassis' in self.changed_data) and virtual_chassis and AccessList.objects.filter(name__iexact=name, virtual_chassis=virtual_chassis).exists():
|
||||
error_message.update(
|
||||
{
|
||||
'virtual_chassis': ['An ACL with this name (case insensitive) is already associated to this host.'],
|
||||
'name': ['An ACL with this name (case insensitive) is already associated to this host.'],
|
||||
}
|
||||
)
|
||||
if ('name' in self.changed_data or 'virtual_machine' in self.changed_data) and virtual_machine and AccessList.objects.filter(name__iexact=name, virtual_machine=virtual_machine).exists():
|
||||
error_message.update(
|
||||
{
|
||||
'virtual_machine': ['An ACL with this name (case insensitive) is already associated to this host.'],
|
||||
'name': ['An ACL with this name (case insensitive) is already associated to this host.'],
|
||||
}
|
||||
)
|
||||
if type == 'extended' and self.instance.aclstandardrules.exists():
|
||||
error_message.update({'type': ['This ACL has Standard ACL rules already associated, CANNOT change ACL type!!']})
|
||||
elif type == 'standard' and self.instance.aclextendedrules.exists():
|
||||
error_message.update({'type': ['This ACL has Extended ACL rules already associated, CANNOT change ACL type!!']})
|
||||
if len(error_message) > 0:
|
||||
|
||||
if device:
|
||||
host_type = 'device'
|
||||
existing_acls = AccessList.objects.filter(name=name, device=device).exists()
|
||||
elif virtual_chassis:
|
||||
host_type = 'virtual_chassis'
|
||||
existing_acls = AccessList.objects.filter(name=name, virtual_chassis=virtual_chassis).exists()
|
||||
elif virtual_machine:
|
||||
host_type = 'virtual_machine'
|
||||
existing_acls = AccessList.objects.filter(name=name, virtual_machine=virtual_machine).exists()
|
||||
host = cleaned_data.get(host_type)
|
||||
|
||||
# Check if duplicate entry.
|
||||
if ('name' in self.changed_data or host_type in self.changed_data) and existing_acls:
|
||||
error_same_acl_name = 'An ACL with this name is already associated to this host.'
|
||||
error_message |= {host_type: [error_same_acl_name], '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()):
|
||||
error_message['type'] = ['This ACL has ACL rules associated, CANNOT change ACL type.']
|
||||
|
||||
if error_message:
|
||||
raise forms.ValidationError(error_message)
|
||||
|
||||
return cleaned_data
|
||||
@ -146,6 +164,150 @@ class AccessListForm(NetBoxModelForm):
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentForm(NetBoxModelForm):
|
||||
"""
|
||||
GUI form to add or edit ACL Host Object assignments
|
||||
Requires an access_list, a name, a type, and a default_action.
|
||||
"""
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
# Need to pass ACL device to it
|
||||
},
|
||||
)
|
||||
interface = DynamicModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
label='Virtual Machine',
|
||||
)
|
||||
vminterface = DynamicModelChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'virtual_machine_id': '$virtual_machine'
|
||||
},
|
||||
label='VM Interface'
|
||||
)
|
||||
#virtual_chassis = DynamicModelChoiceField(
|
||||
# queryset=VirtualChassis.objects.all(),
|
||||
# required=False,
|
||||
# label='Virtual Chassis',
|
||||
#)
|
||||
access_list = DynamicModelChoiceField(
|
||||
queryset=AccessList.objects.all(),
|
||||
#query_params={
|
||||
# 'assigned_object': '$device',
|
||||
# 'assigned_object': '$virtual_machine',
|
||||
#},
|
||||
label='Access List',
|
||||
help_text=mark_safe('<b>*Note:</b> Access List must be present on the device already.')
|
||||
)
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Initialize helper selectors
|
||||
instance = kwargs.get('instance')
|
||||
initial = kwargs.get('initial', {}).copy()
|
||||
if instance:
|
||||
if type(instance.assigned_object) is Interface:
|
||||
initial['interface'] = instance.assigned_object
|
||||
initial['device'] = 'device'
|
||||
elif type(instance.assigned_object) is VMInterface:
|
||||
initial['vminterface'] = instance.assigned_object
|
||||
initial['virtual_machine'] = 'virtual_machine'
|
||||
kwargs['initial'] = initial
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = ACLInterfaceAssignment
|
||||
fields = (
|
||||
'access_list', 'direction', 'device', 'interface', 'virtual_machine',
|
||||
'vminterface', 'comments', 'tags',
|
||||
)
|
||||
help_texts = {
|
||||
'direction': mark_safe('<b>*Note:</b> CANNOT assign 2 ACLs to the same interface & direction.'),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Validates form inputs before submitting:
|
||||
- Check if both interface and vminterface are set.
|
||||
- Check if neither interface or 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)
|
||||
- Check that the interface does not have an existing ACL applied in the direction already.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
access_list = cleaned_data.get('access_list')
|
||||
direction = cleaned_data.get('direction')
|
||||
interface = cleaned_data.get('interface')
|
||||
vminterface = cleaned_data.get('vminterface')
|
||||
assigned_object = cleaned_data.get('assigned_object')
|
||||
if interface:
|
||||
assigned_object = interface
|
||||
assigned_object_type = 'interface'
|
||||
host_type = 'device'
|
||||
host = Interface.objects.get(pk=assigned_object.pk).device
|
||||
elif vminterface:
|
||||
assigned_object = vminterface
|
||||
assigned_object_type = 'vminterface'
|
||||
host_type = 'virtual_machine'
|
||||
host = VMInterface.objects.get(pk=assigned_object.pk).virtual_machine
|
||||
if interface or vminterface:
|
||||
assigned_object_id = VMInterface.objects.get(pk=assigned_object.pk).pk
|
||||
assigned_object_type_id = ContentType.objects.get_for_model(assigned_object).pk
|
||||
access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object
|
||||
|
||||
# Check if both interface and vminterface are set.
|
||||
if interface and vminterface:
|
||||
error_too_many_interfaces = 'Access Lists must be assigned to one type of interface at a time (VM interface or physical interface)'
|
||||
error_too_many_hosts = 'Access Lists must be assigned to one type of device at a time (VM or physical device).'
|
||||
error_message |= {'device': [error_too_many_hosts], 'interface': [error_too_many_interfaces], 'virtual_machine': [error_too_many_hosts], 'vminterface': [error_too_many_interfaces]}
|
||||
# Check if neither interface or vminterface are set.
|
||||
elif not (interface or vminterface):
|
||||
error_no_interface = 'An Access List assignment but specify an Interface or VM Interface.'
|
||||
error_message |= {'interface': [error_no_interface], 'vminterface': [error_no_interface]}
|
||||
# Check that an interface's parent device/virtual_machine is assigned to the Access List.
|
||||
elif 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.
|
||||
elif 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.
|
||||
elif 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)
|
||||
return cleaned_data
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Set assigned object
|
||||
self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class ACLStandardRuleForm(NetBoxModelForm):
|
||||
"""
|
||||
GUI form to add or edit Standard Access List.
|
||||
@ -163,7 +325,7 @@ class ACLStandardRuleForm(NetBoxModelForm):
|
||||
source_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
help_text=acl_rule_logic_help,
|
||||
help_text=help_text_acl_rule_logic,
|
||||
label='Source Prefix',
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
@ -183,27 +345,35 @@ class ACLStandardRuleForm(NetBoxModelForm):
|
||||
'tags', 'description'
|
||||
)
|
||||
help_texts = {
|
||||
'index': 'Determines the order of the rule in the ACL processing.',
|
||||
'index': help_text_acl_rule_index,
|
||||
'action': help_text_acl_action,
|
||||
'remark': mark_safe('<b>*Note:</b> CANNOT be set if source prefix OR action is set.'),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Validates form inputs before submitting.
|
||||
If action is set to remark, remark needs to be set.
|
||||
If action is set to remark, source_prefix cannot be set.
|
||||
If action is not set to remark, remark cannot be set.
|
||||
Validates form inputs before submitting:
|
||||
- Check if action set to remark, but no remark set.
|
||||
- Check if action set to remark, but source_prefix set.
|
||||
- Check remark set, but action not set to remark.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
|
||||
# No need to check for unique_together since there is no usage of GFK
|
||||
|
||||
if cleaned_data.get('action') == 'remark':
|
||||
if cleaned_data.get('remark') is None:
|
||||
error_message.update({'remark': ['Action is set to remark, you MUST add a remark.']})
|
||||
# Check if action set to remark, but no remark set.
|
||||
if not cleaned_data.get('remark'):
|
||||
error_message['remark'] = [error_message_no_remark]
|
||||
# Check if action set to remark, but source_prefix set.
|
||||
if cleaned_data.get('source_prefix'):
|
||||
error_message.update({'source_prefix': ['Action is set to remark, Source Prefix CANNOT be set.']})
|
||||
error_message['source_prefix'] = [error_message_action_remark_source_prefix_set]
|
||||
# Check remark set, but action not set to remark.
|
||||
elif cleaned_data.get('remark'):
|
||||
error_message.update({'remark': ['CANNOT set remark unless action is set to remark, .']})
|
||||
if len(error_message) > 0:
|
||||
error_message['remark'] = [error_message_remark_without_action_remark]
|
||||
|
||||
if error_message:
|
||||
raise forms.ValidationError(error_message)
|
||||
return cleaned_data
|
||||
|
||||
@ -229,13 +399,13 @@ class ACLExtendedRuleForm(NetBoxModelForm):
|
||||
source_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
help_text=acl_rule_logic_help,
|
||||
help_text=help_text_acl_rule_logic,
|
||||
label='Source Prefix',
|
||||
)
|
||||
destination_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
help_text=acl_rule_logic_help,
|
||||
help_text=help_text_acl_rule_logic,
|
||||
label='Destination Prefix',
|
||||
)
|
||||
fieldsets = (
|
||||
@ -251,37 +421,54 @@ class ACLExtendedRuleForm(NetBoxModelForm):
|
||||
'tags', 'description'
|
||||
)
|
||||
help_texts = {
|
||||
'destination_ports': acl_rule_logic_help,
|
||||
'index': 'Determines the order of the rule in the ACL processing.',
|
||||
'protocol': acl_rule_logic_help,
|
||||
'action': help_text_acl_action,
|
||||
'destination_ports': help_text_acl_rule_logic,
|
||||
'index': help_text_acl_rule_index,
|
||||
'protocol': help_text_acl_rule_logic,
|
||||
'remark': mark_safe('<b>*Note:</b> CANNOT be set if action is not set to remark.'),
|
||||
'source_ports': acl_rule_logic_help,
|
||||
'source_ports': help_text_acl_rule_logic,
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Validates form inputs before submitting.
|
||||
If action is set to remark, remark needs to be set.
|
||||
If action is set to remark, source_prefix, source_ports, desintation_prefix, destination_ports, or protocol cannot be set.
|
||||
If action is not set to remark, remark cannot be set.
|
||||
Validates form inputs before submitting:
|
||||
- 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.
|
||||
- Check if action set to remark, but destination_prefix set.
|
||||
- Check if action set to remark, but destination_ports set.
|
||||
- Check if action set to remark, but destination_ports set.
|
||||
- Check if action set to remark, but protocol set.
|
||||
- Check remark set, but action not set to remark.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
|
||||
# No need to check for unique_together since there is no usage of GFK
|
||||
|
||||
if cleaned_data.get('action') == 'remark':
|
||||
if cleaned_data.get('remark') is None:
|
||||
error_message.update({'remark': ['Action is set to remark, you MUST add a remark.']})
|
||||
# Check if action set to remark, but no remark set.
|
||||
if not cleaned_data.get('remark'):
|
||||
error_message['remark'] = [error_message_no_remark]
|
||||
# Check if action set to remark, but source_prefix set.
|
||||
if cleaned_data.get('source_prefix'):
|
||||
error_message.update({'source_prefix': ['Action is set to remark, Source Prefix CANNOT be set.']})
|
||||
error_message['source_prefix'] = [error_message_action_remark_source_prefix_set]
|
||||
# Check if action set to remark, but source_ports set.
|
||||
if cleaned_data.get('source_ports'):
|
||||
error_message.update({'source_ports': ['Action is set to remark, Source Ports CANNOT be set.']})
|
||||
error_message['source_ports'] = ['Action is set to remark, Source Ports CANNOT be set.']
|
||||
# Check if action set to remark, but destination_prefix set.
|
||||
if cleaned_data.get('destination_prefix'):
|
||||
error_message.update({'destination_prefix': ['Action is set to remark, Destination Prefix CANNOT be set.']})
|
||||
error_message['destination_prefix'] = ['Action is set to remark, Destination Prefix CANNOT be set.']
|
||||
# Check if action set to remark, but destination_ports set.
|
||||
if cleaned_data.get('destination_ports'):
|
||||
error_message.update({'destination_ports': ['Action is set to remark, Destination Ports CANNOT be set.']})
|
||||
error_message['destination_ports'] = ['Action is set to remark, Destination Ports CANNOT be set.']
|
||||
# Check if action set to remark, but protocol set.
|
||||
if cleaned_data.get('protocol'):
|
||||
error_message.update({'protocol': ['Action is set to remark, Protocol CANNOT be set.']})
|
||||
error_message['protocol'] = ['Action is set to remark, Protocol CANNOT be set.']
|
||||
# Check if action not set to remark, but remark set.
|
||||
elif cleaned_data.get('remark'):
|
||||
error_message.update({'remark': ['CANNOT set remark unless action is set to remark, .']})
|
||||
if len(error_message) > 0:
|
||||
error_message['remark'] = [error_message_remark_without_action_remark]
|
||||
|
||||
if error_message:
|
||||
raise forms.ValidationError(error_message)
|
||||
return cleaned_data
|
||||
|
||||
@ -10,6 +10,7 @@ from . import filtersets, models
|
||||
|
||||
__all__ = (
|
||||
'AccessListType',
|
||||
'ACLInterfaceAssignmentType',
|
||||
'ACLExtendedRuleType',
|
||||
'ACLStandardRuleType',
|
||||
)
|
||||
@ -33,6 +34,20 @@ class AccessListType(NetBoxObjectType):
|
||||
filterset_class = filtersets.AccessListFilterSet
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentType(NetBoxObjectType):
|
||||
"""
|
||||
Defines the object type for the django model AccessList.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the filterset, fields, and model for the django model ACLInterfaceAssignment.
|
||||
"""
|
||||
model = models.ACLInterfaceAssignment
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ACLInterfaceAssignmentFilterSet
|
||||
|
||||
|
||||
class ACLExtendedRuleType(NetBoxObjectType):
|
||||
"""
|
||||
Defines the object type for the django model ACLExtendedRule.
|
||||
|
||||
@ -32,7 +32,7 @@ class Migration(migrations.Migration):
|
||||
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('name', models.CharField(max_length=500)),
|
||||
('assigned_object_id', models.PositiveIntegerField()),
|
||||
('assigned_object_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')),
|
||||
('type', models.CharField(max_length=100)),
|
||||
@ -46,6 +46,26 @@ class Migration(migrations.Migration):
|
||||
'verbose_name': 'Access List',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ACLInterfaceAssignment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('access_list', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='aclinterfaceassignment', to='netbox_access_lists.accesslist')),
|
||||
('direction', models.CharField(max_length=100)),
|
||||
('assigned_object_id', models.PositiveIntegerField()),
|
||||
('assigned_object_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('access_list', 'assigned_object_type', 'assigned_object_id', 'direction'),
|
||||
'unique_together': {('assigned_object_type', 'assigned_object_id', 'access_list', 'direction')},
|
||||
'verbose_name': 'ACL Interface Assignment',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ACLStandardRule',
|
||||
fields=[
|
||||
|
||||
@ -26,12 +26,12 @@ class ACLRule(NetBoxModel):
|
||||
on_delete=models.CASCADE,
|
||||
to=AccessList,
|
||||
verbose_name='Access List',
|
||||
related_name='rules',
|
||||
)
|
||||
index = models.PositiveIntegerField()
|
||||
remark = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
null=True
|
||||
max_length=500,
|
||||
blank=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=500,
|
||||
@ -72,6 +72,13 @@ class ACLStandardRule(ACLRule):
|
||||
"""
|
||||
Inherits ACLRule.
|
||||
"""
|
||||
access_list = models.ForeignKey(
|
||||
on_delete=models.CASCADE,
|
||||
to=AccessList,
|
||||
verbose_name='Standard Access List',
|
||||
limit_choices_to={'type': 'standard'},
|
||||
related_name='aclstandardrules',
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""
|
||||
@ -87,7 +94,6 @@ class ACLStandardRule(ACLRule):
|
||||
- verbose name (for displaying in the GUI)
|
||||
- verbose name plural (for displaying in the GUI)
|
||||
"""
|
||||
default_related_name='aclstandardrules'
|
||||
verbose_name='ACL Standard Rule'
|
||||
verbose_name_plural='ACL Standard Rules'
|
||||
|
||||
@ -96,6 +102,13 @@ class ACLExtendedRule(ACLRule):
|
||||
Inherits ACLRule.
|
||||
Add ACLExtendedRule specific fields: source_ports, desintation_prefix, destination_ports, and protocol
|
||||
"""
|
||||
access_list = models.ForeignKey(
|
||||
on_delete=models.CASCADE,
|
||||
to=AccessList,
|
||||
verbose_name='Extended Access List',
|
||||
limit_choices_to={'type': 'extended'},
|
||||
related_name='aclextendedrules',
|
||||
)
|
||||
source_ports = ArrayField(
|
||||
base_field=models.PositiveIntegerField(),
|
||||
blank=True,
|
||||
@ -139,6 +152,5 @@ class ACLExtendedRule(ACLRule):
|
||||
- verbose name (for displaying in the GUI)
|
||||
- verbose name plural (for displaying in the GUI)
|
||||
"""
|
||||
default_related_name='aclextendedrules'
|
||||
verbose_name='ACL Extended Rule'
|
||||
verbose_name_plural='ACL Extended Rules'
|
||||
|
||||
@ -2,29 +2,35 @@
|
||||
Define the django models for this plugin.
|
||||
"""
|
||||
|
||||
from dcim.models import Device, VirtualChassis
|
||||
from dcim.models import Device, Interface, VirtualChassis
|
||||
from django.contrib.contenttypes.fields import (GenericForeignKey,
|
||||
GenericRelation)
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from netbox.models import NetBoxModel
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
|
||||
from ..choices import *
|
||||
from ..constants import ACL_HOST_ASSIGNMENT_MODELS
|
||||
from ..constants import (ACL_HOST_ASSIGNMENT_MODELS,
|
||||
ACL_INTERFACE_ASSIGNMENT_MODELS)
|
||||
|
||||
__all__ = (
|
||||
'AccessList',
|
||||
'ACLInterfaceAssignment',
|
||||
)
|
||||
|
||||
|
||||
alphanumeric_plus = RegexValidator(r'^[0-9a-zA-Z,-,_]*$', 'Only alphanumeric, hyphens, and underscores characters are allowed.')
|
||||
|
||||
class AccessList(NetBoxModel):
|
||||
"""
|
||||
Model defintion for Access Lists.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
max_length=500,
|
||||
validators=[alphanumeric_plus]
|
||||
)
|
||||
assigned_object_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
@ -53,7 +59,8 @@ class AccessList(NetBoxModel):
|
||||
class Meta:
|
||||
unique_together = ['assigned_object_type', 'assigned_object_id', 'name']
|
||||
ordering = ['assigned_object_type', 'assigned_object_id', 'name']
|
||||
verbose_name = "Access List"
|
||||
verbose_name = 'Access List'
|
||||
verbose_name_plural = 'Access Lists'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -72,6 +79,68 @@ class AccessList(NetBoxModel):
|
||||
return ACLTypeChoices.colors.get(self.type)
|
||||
|
||||
|
||||
class ACLInterfaceAssignment(NetBoxModel):
|
||||
"""
|
||||
Model defintion for Access Lists associations with other Host interfaces:
|
||||
- VM interfaces
|
||||
- device interface
|
||||
- tbd on more
|
||||
"""
|
||||
|
||||
access_list = models.ForeignKey(
|
||||
on_delete=models.CASCADE,
|
||||
to=AccessList,
|
||||
verbose_name='Access List',
|
||||
)
|
||||
direction = models.CharField(
|
||||
max_length=30,
|
||||
choices=ACLAssignmentDirectionChoices
|
||||
)
|
||||
assigned_object_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to=ACL_INTERFACE_ASSIGNMENT_MODELS,
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
assigned_object_id = models.PositiveBigIntegerField()
|
||||
assigned_object = GenericForeignKey(
|
||||
ct_field='assigned_object_type',
|
||||
fk_field='assigned_object_id'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ['assigned_object_type', 'assigned_object_id', 'access_list', 'direction']
|
||||
ordering = ['assigned_object_type', 'assigned_object_id', 'access_list', 'direction']
|
||||
verbose_name = 'ACL Interface Assignment'
|
||||
verbose_name_plural = 'ACL Interface Assignments'
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""
|
||||
The method is a Django convention; although not strictly required,
|
||||
it conveniently returns the absolute URL for any particular object.
|
||||
"""
|
||||
return reverse('plugins:netbox_access_lists:aclinterfaceassignment', args=[self.pk])
|
||||
|
||||
def get_direction_color(self):
|
||||
return ACLAssignmentDirectionChoices.colors.get(self.direction)
|
||||
|
||||
|
||||
GenericRelation(
|
||||
to=ACLInterfaceAssignment,
|
||||
content_type_field='assigned_object_type',
|
||||
object_id_field='assigned_object_id',
|
||||
related_query_name='interface'
|
||||
).contribute_to_class(Interface, 'accesslistassignments')
|
||||
|
||||
GenericRelation(
|
||||
to=ACLInterfaceAssignment,
|
||||
content_type_field='assigned_object_type',
|
||||
object_id_field='assigned_object_id',
|
||||
related_query_name='vminterface'
|
||||
).contribute_to_class(VMInterface, 'accesslistassignments')
|
||||
|
||||
GenericRelation(
|
||||
to=AccessList,
|
||||
content_type_field='assigned_object_type',
|
||||
|
||||
@ -36,6 +36,15 @@ aclextendedrule_butons = [
|
||||
)
|
||||
]
|
||||
|
||||
accesslistassignment_buttons = [
|
||||
PluginMenuButton(
|
||||
link='plugins:netbox_access_lists:aclinterfaceassignment_add',
|
||||
title='Add',
|
||||
icon_class='mdi mdi-plus-thick',
|
||||
color=ButtonColorChoices.GREEN
|
||||
)
|
||||
]
|
||||
|
||||
#
|
||||
# Define navigation bar links including the above buttons defined.
|
||||
#
|
||||
@ -49,7 +58,7 @@ menu_items = (
|
||||
# Comment out Standard Access List rule to force creation in the ACL view
|
||||
PluginMenuItem(
|
||||
link='plugins:netbox_access_lists:aclstandardrule_list',
|
||||
link_text='ACL Standard Rule',
|
||||
link_text='ACL Standard Rules',
|
||||
buttons=aclstandardrule_butons
|
||||
),
|
||||
# Comment out Extended Access List rule to force creation in the ACL view
|
||||
@ -58,4 +67,9 @@ menu_items = (
|
||||
link_text='ACL Extended Rules',
|
||||
buttons=aclextendedrule_butons
|
||||
),
|
||||
PluginMenuItem(
|
||||
link='plugins:netbox_access_lists:aclinterfaceassignment_list',
|
||||
link_text='ACL Interface Assignments',
|
||||
buttons=accesslistassignment_buttons
|
||||
),
|
||||
)
|
||||
|
||||
@ -3,23 +3,34 @@ Define the object lists / table view for each of the plugin models.
|
||||
"""
|
||||
|
||||
import django_tables2 as tables
|
||||
from netbox.tables import ChoiceFieldColumn, NetBoxTable, columns
|
||||
from netbox.tables import (ChoiceFieldColumn, NetBoxTable, TemplateColumn,
|
||||
columns)
|
||||
|
||||
from .models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
from .models import (AccessList, ACLExtendedRule, ACLInterfaceAssignment,
|
||||
ACLStandardRule)
|
||||
|
||||
__all__ = (
|
||||
'AccessListTable',
|
||||
'ACLInterfaceAssignmentTable',
|
||||
'ACLStandardRuleTable',
|
||||
'ACLExtendedRuleTable',
|
||||
)
|
||||
|
||||
|
||||
COL_HOST_ASSIGNMENT = """
|
||||
{% if record.assigned_object.device %}
|
||||
<a href="{{ record.assigned_object.device.get_absolute_url }}">{{ record.assigned_object.device|placeholder }}</a>
|
||||
{% else %}
|
||||
<a href="{{ record.assigned_object.virtual_machine.get_absolute_url }}">{{ record.assigned_object.virtual_machine|placeholder }}</a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
class AccessListTable(NetBoxTable):
|
||||
"""
|
||||
Defines the table view for the AccessList model.
|
||||
"""
|
||||
pk = columns.ToggleColumn()
|
||||
id = tables.Column( # Provides a link to the secret
|
||||
id = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
assigned_object = tables.Column(
|
||||
@ -48,6 +59,37 @@ class AccessListTable(NetBoxTable):
|
||||
default_columns = ('name', 'assigned_object', 'type', 'rule_count', 'default_action', 'tags')
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentTable(NetBoxTable):
|
||||
"""
|
||||
Defines the table view for the AccessList model.
|
||||
"""
|
||||
pk = columns.ToggleColumn()
|
||||
id = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
access_list = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
direction = ChoiceFieldColumn()
|
||||
host = tables.TemplateColumn(
|
||||
template_code=COL_HOST_ASSIGNMENT
|
||||
|
||||
)
|
||||
assigned_object = tables.Column(
|
||||
linkify=True,
|
||||
orderable=False,
|
||||
verbose_name='Assigned Interface'
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='plugins:netbox_access_lists:aclinterfaceassignment_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ACLInterfaceAssignment
|
||||
fields = ('pk', 'id', 'access_list', 'direction', 'host', 'assigned_object', 'tags')
|
||||
default_columns = ('id', 'access_list', 'direction', 'host', 'assigned_object', 'tags')
|
||||
|
||||
|
||||
class ACLStandardRuleTable(NetBoxTable):
|
||||
"""
|
||||
Defines the table view for the ACLStandardRule model.
|
||||
|
||||
@ -2,16 +2,35 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from extras.plugins import PluginTemplateExtension
|
||||
|
||||
from .models import AccessList
|
||||
from .models import AccessList, ACLInterfaceAssignment
|
||||
|
||||
__all__ = (
|
||||
'AccessLists',
|
||||
"ACLInterfaceAssignments",
|
||||
'DeviceAccessLists',
|
||||
'VirtualChassisAccessLists',
|
||||
'VMAccessLists',
|
||||
'DeviceACLInterfaceAssignments',
|
||||
'VMAACLInterfaceAssignments',
|
||||
)
|
||||
|
||||
|
||||
class ACLInterfaceAssignments(PluginTemplateExtension):
|
||||
|
||||
def right_page(self):
|
||||
obj = self.context['object']
|
||||
|
||||
acl_interface_assignments = None
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
if ctype.model in ['interface', 'vminterface']:
|
||||
acl_interface_assignments = ACLInterfaceAssignment.objects.filter(assigned_object_id=obj.pk, assigned_object_type=ctype)
|
||||
|
||||
return self.render('inc/assigned_interface/access_lists.html', extra_context={
|
||||
'acl_interface_assignments': acl_interface_assignments,
|
||||
'type': ctype.model if ctype.model == 'device' else ctype.name.replace(' ', '_'),
|
||||
})
|
||||
|
||||
|
||||
class AccessLists(PluginTemplateExtension):
|
||||
|
||||
def right_page(self):
|
||||
@ -19,11 +38,7 @@ class AccessLists(PluginTemplateExtension):
|
||||
|
||||
access_lists = None
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
if ctype.model == 'device':
|
||||
access_lists = AccessList.objects.filter(assigned_object_id=obj.pk, assigned_object_type=ctype)
|
||||
elif ctype.model == 'virtualchassis':
|
||||
access_lists = AccessList.objects.filter(assigned_object_id=obj.pk, assigned_object_type=ctype)
|
||||
elif ctype.model == 'virtualmachine':
|
||||
if ctype.model in ['device', 'virtualchassis', 'virtualmachine']:
|
||||
access_lists = AccessList.objects.filter(assigned_object_id=obj.pk, assigned_object_type=ctype)
|
||||
|
||||
return self.render('inc/assigned_host/access_lists.html', extra_context={
|
||||
@ -44,4 +59,12 @@ class VMAccessLists(AccessLists):
|
||||
model = 'virtualization.virtualmachine'
|
||||
|
||||
|
||||
template_extensions = [DeviceAccessLists, VirtualChassisAccessLists, VMAccessLists]
|
||||
class DeviceACLInterfaceAssignments(ACLInterfaceAssignments):
|
||||
model = 'dcim.interface'
|
||||
|
||||
|
||||
class VMAACLInterfaceAssignments(ACLInterfaceAssignments):
|
||||
model = 'virtualization.vminterface'
|
||||
|
||||
|
||||
template_extensions = [DeviceAccessLists, VirtualChassisAccessLists, VMAccessLists, DeviceACLInterfaceAssignments, VMAACLInterfaceAssignments]
|
||||
|
||||
@ -7,15 +7,15 @@
|
||||
<th>Default Action</th>
|
||||
<th>Rule Count</th>
|
||||
</tr>
|
||||
{% for access_list in access_lists %}
|
||||
{% for object in access_lists %}
|
||||
<tr>
|
||||
<td>{{ access_list|linkify }}</td>
|
||||
<td>{{ access_list.type|title }}</td>
|
||||
<td>{{ access_list.default_action|title }}</td>
|
||||
{% if access_list.type == 'standard' %}
|
||||
<td>{{ access_list.aclstandardrules.count|placeholder }}</td>
|
||||
{% elif access_list.type == 'extended' %}
|
||||
<td>{{ access_list.aclextendedrules.count|placeholder }}</td>
|
||||
<td>{{ object|linkify }}</td>
|
||||
<td>{{ object.type|title }}</td>
|
||||
<td>{{ object.default_action|title }}</td>
|
||||
{% if object.type == 'standard' %}
|
||||
<td>{{ object.aclstandardrules.count|placeholder }}</td>
|
||||
{% elif object.type == 'extended' %}
|
||||
<td>{{ object.aclextendedrules.count|placeholder }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -24,4 +24,4 @@
|
||||
<div class="text-muted">
|
||||
None found
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -0,0 +1,13 @@
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
Access Lists
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
{% include 'inc/assigned_interface/assigned_access_lists.html' %}
|
||||
</div>
|
||||
<div class="card-footer text-end noprint">
|
||||
<a href="{% url 'plugins:netbox_access_lists:aclinterfaceassignment_add' %}?{{ type }}={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">
|
||||
<i class="mdi mdi-plus-thick"></i> Assign Access List
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,29 @@
|
||||
{% if acl_interface_assignments %}
|
||||
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Default Action</th>
|
||||
<th>Rule Count</th>
|
||||
<th>Direction</th>
|
||||
</tr>
|
||||
{% for object in acl_interface_assignments %}
|
||||
<tr>
|
||||
<td>{{ object.access_list|linkify }}</td>
|
||||
<td>{{ object.access_list.type|title }}</td>
|
||||
<td>{{ object.access_list.default_action|title }}</td>
|
||||
{% if object.access_list.type == 'standard' %}
|
||||
<td>{{ object.access_list.aclstandardrules.count|placeholder }}</td>
|
||||
{% elif object.access_list.type == 'extended' %}
|
||||
<td>{{ object.access_list.aclextendedrules.count|placeholder }}</td>
|
||||
{% endif %}
|
||||
<td>{{ object.direction|title }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="text-muted">
|
||||
None
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -2,7 +2,7 @@
|
||||
{% load static %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}{% if obj.pk %}Editing {{ obj }}{% else %}Add a Access List{% endif %}{% endblock %}
|
||||
{% block title %}{% if obj.pk %}Editing {{ obj }}{% else %}Add an Access List{% endif %}{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
{% render_errors form %}
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li class="breadcrumb-item"><a href="{% url 'plugins:netbox_access_lists:aclinterfaceassignment_list' %}">ACL Interface Assignments</a></li>
|
||||
{% endblock %}
|
||||
{% block controls %}
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.netbox_access_lists.change_policy %}
|
||||
<a href="{% url 'plugins:netbox_access_lists:aclinterfaceassignment_edit' pk=object.pk %}" class="btn btn-warning">
|
||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.netbox_access_lists.delete_policy %}
|
||||
<a href="{% url 'plugins:netbox_access_lists:aclinterfaceassignment_delete' pk=object.pk %}" class="btn btn-danger">
|
||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock controls %}
|
||||
{% block tabs %}
|
||||
<ul class="nav nav-tabs px-3">
|
||||
{% block tab_items %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||
</li>
|
||||
{% endblock tab_items %}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{% url 'plugins:netbox_access_lists:aclinterfaceassignment_changelog' pk=object.pk %}" class="nav-link{% if active_tab == 'changelog'%} active{% endif %}">Change Log</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock tabs %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">ACL Interface Assignment</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Host</th>
|
||||
<td>
|
||||
{% if object.assigned_object.device %}
|
||||
<a href="{{ object.assigned_object.device.get_absolute_url }}">{{ object.assigned_object.device|placeholder }}</a>
|
||||
{% else %}
|
||||
<a href="{{ object.assigned_object.virtual_machine.get_absolute_url }}">{{ object.assigned_object.virtual_machine|placeholder }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Interface</th>
|
||||
<td>
|
||||
<a href="{{ object.assigned_object.get_absolute_url }}">{{ object.assigned_object }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Access List</th>
|
||||
<td>
|
||||
<a href="{{ object.access_list.get_absolute_url }}">{{ object.access_list }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Direction</th>
|
||||
<td>
|
||||
{{ object.direction|title }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@ -0,0 +1,68 @@
|
||||
{% extends 'generic/object_edit.html' %}
|
||||
{% load static %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}{% if obj.pk %}Editing {{ obj }}{% else %}Add an Access List to an Interface{% endif %}{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
{% render_errors form %}
|
||||
<div class="field-group">
|
||||
<h4>Access List Details</h4>
|
||||
{% render_field form.access_list %}
|
||||
{% render_field form.direction %}
|
||||
{% render_field form.tags %}
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<h4>Interface Assignment</h4>
|
||||
<ul class="nav nav-pills" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
role="tab"
|
||||
type="button"
|
||||
id="device_tab"
|
||||
data-bs-toggle="tab"
|
||||
class="nav-link {% if not form.initial.virtual_chassis and not form.initial.virtual_machine %}active{% endif %}"
|
||||
data-bs-target="#device"
|
||||
aria-controls="device"
|
||||
>
|
||||
Device
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
role="tab"
|
||||
type="button"
|
||||
id="vm_tab"
|
||||
data-bs-toggle="tab"
|
||||
class="nav-link {% if form.initial.virtual_machine %}active{% endif %}"
|
||||
data-bs-target="#virtualmachine"
|
||||
aria-controls="virtualmachine"
|
||||
>
|
||||
Virtual Machine
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane{% if not form.initial.virtual_chassis and not form.initial.virtualmachine %} active{% endif %}" id="device">
|
||||
{% render_field form.device %}
|
||||
{% render_field form.interface %}
|
||||
</div>
|
||||
<div class="tab-pane{% if form.initial.virtual_machine %} active{% endif %}" id="virtualmachine">
|
||||
{% render_field form.virtual_machine %}
|
||||
{% render_field form.vminterface %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<h4>Comments</h4>
|
||||
{% render_field form.comments %}
|
||||
</div>
|
||||
{% if form.custom_fields %}
|
||||
<div class="card">
|
||||
<h5 class="card-header">Custom Fields</h5>
|
||||
<div class="card-body">
|
||||
{% render_custom_fields form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@ -21,7 +21,19 @@ urlpatterns = (
|
||||
'model': models.AccessList
|
||||
}),
|
||||
|
||||
# Standard Access List rules
|
||||
# Access List Interface Assignments
|
||||
path('interface-assignments/', views.ACLInterfaceAssignmentListView.as_view(), name='aclinterfaceassignment_list'),
|
||||
path('interface-assignments/add/', views.ACLInterfaceAssignmentEditView.as_view(), name='aclinterfaceassignment_add'),
|
||||
#path('interface-assignments/edit/', views.ACLInterfaceAssignmentBulkEditView.as_view(), name='aclinterfaceassignment_bulk_edit'),
|
||||
path('interface-assignments/delete/', views.ACLInterfaceAssignmentBulkDeleteView.as_view(), name='aclinterfaceassignment_bulk_delete'),
|
||||
path('interface-assignments/<int:pk>/', views.ACLInterfaceAssignmentView.as_view(), name='aclinterfaceassignment'),
|
||||
path('interface-assignments/<int:pk>/edit/', views.ACLInterfaceAssignmentEditView.as_view(), name='aclinterfaceassignment_edit'),
|
||||
path('interface-assignments/<int:pk>/delete/', views.ACLInterfaceAssignmentDeleteView.as_view(), name='aclinterfaceassignment_delete'),
|
||||
path('interface-assignments/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aclinterfaceassignment_changelog', kwargs={
|
||||
'model': models.ACLInterfaceAssignment
|
||||
}),
|
||||
|
||||
# Standard Access List Rules
|
||||
path('standard-rules/', views.ACLStandardRuleListView.as_view(), name='aclstandardrule_list'),
|
||||
path('standard-rules/add/', views.ACLStandardRuleEditView.as_view(), name='aclstandardrule_add'),
|
||||
path('standard-rules/delete/', views.ACLStandardRuleBulkDeleteView.as_view(), name='aclstandardrule_bulk_delete'),
|
||||
@ -32,7 +44,7 @@ urlpatterns = (
|
||||
'model': models.ACLStandardRule
|
||||
}),
|
||||
|
||||
# Extended Access List rules
|
||||
# Extended Access List Rules
|
||||
path('extended-rules/', views.ACLExtendedRuleListView.as_view(), name='aclextendedrule_list'),
|
||||
path('extended-rules/add/', views.ACLExtendedRuleEditView.as_view(), name='aclextendedrule_add'),
|
||||
path('extended-rules/delete/', views.ACLExtendedRuleBulkDeleteView.as_view(), name='aclextendedrule_bulk_delete'),
|
||||
|
||||
@ -14,6 +14,11 @@ __all__ = (
|
||||
'AccessListEditView',
|
||||
'AccessListDeleteView',
|
||||
'AccessListBulkDeleteView',
|
||||
'ACLInterfaceAssignmentView',
|
||||
'ACLInterfaceAssignmentListView',
|
||||
'ACLInterfaceAssignmentEditView',
|
||||
'ACLInterfaceAssignmentDeleteView',
|
||||
'ACLInterfaceAssignmentBulkDeleteView',
|
||||
'ACLStandardRuleView',
|
||||
'ACLStandardRuleListView',
|
||||
'ACLStandardRuleEditView',
|
||||
@ -85,17 +90,48 @@ class AccessListBulkDeleteView(generic.BulkDeleteView):
|
||||
filterset = filtersets.AccessListFilterSet
|
||||
table = tables.AccessListTable
|
||||
|
||||
#
|
||||
# ACLInterfaceAssignment views
|
||||
#
|
||||
|
||||
class ACLInterfaceAssignmentView(generic.ObjectView):
|
||||
"""
|
||||
Defines the view for the ACLInterfaceAssignments django model.
|
||||
"""
|
||||
queryset = models.ACLInterfaceAssignment.objects.all()
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentListView(generic.ObjectListView):
|
||||
"""
|
||||
Defines the list view for the ACLInterfaceAssignments django model.
|
||||
"""
|
||||
queryset = models.ACLInterfaceAssignment.objects.all()
|
||||
table = tables.ACLInterfaceAssignmentTable
|
||||
filterset = filtersets.ACLInterfaceAssignmentFilterSet
|
||||
filterset_form = forms.ACLInterfaceAssignmentFilterForm
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentEditView(generic.ObjectEditView):
|
||||
"""
|
||||
Defines the edit view for the ACLInterfaceAssignments django model.
|
||||
"""
|
||||
queryset = models.ACLInterfaceAssignment.objects.all()
|
||||
form = forms.ACLInterfaceAssignmentForm
|
||||
template_name = 'netbox_access_lists/aclinterfaceassignment_edit.html'
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentDeleteView(generic.ObjectDeleteView):
|
||||
"""
|
||||
Defines the delete view for the ACLInterfaceAssignments django model.
|
||||
"""
|
||||
queryset = models.ACLInterfaceAssignment.objects.all()
|
||||
|
||||
|
||||
class ACLInterfaceAssignmentBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = models.ACLInterfaceAssignment.objects.all()
|
||||
filterset = filtersets.ACLInterfaceAssignmentFilterSet
|
||||
table = tables.ACLInterfaceAssignmentTable
|
||||
|
||||
#class AccessListBulkEditView(generic.BulkEditView):
|
||||
# """
|
||||
# Defines the bulk edit view for the AccessList django model.
|
||||
# """
|
||||
# queryset = models.AccessList.objects.annotate(
|
||||
# rule_count=Count('aclextendedrules') + Count('aclstandardrules')
|
||||
# )
|
||||
# table = tables.AccessListTable
|
||||
# filterset = filtersets.AccessListFilterSet
|
||||
# form = forms.AccessListBulkEditForm
|
||||
|
||||
#
|
||||
# ACLStandardRule views
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user