mirror of
https://github.com/lucaspalomodevelop/netbox-acls.git
synced 2026-03-13 07:29:40 +00:00
Merge pull request #40 from ryanmerolle/2_ACLRule_models
Closes [Feature]: Add more advanced ACLRule logic #29 Closes [Bug]: Add comment section title to ACL add page #27 Closes [Bug]: Filtering multiple choices not working #15 Closes [Feature]: Limit ACL Rules input options based on ACL type #2
This commit is contained in:
commit
00c5368bb8
@ -27,30 +27,40 @@
|
||||
//"python.sortImports.args": [
|
||||
// "--profile=black"
|
||||
//],
|
||||
//"python.sortImports.path": "/opt/netbox/venv/bin/isort",
|
||||
"python.sortImports.path": "/opt/netbox/venv/bin/isort",
|
||||
"python.analysis.typeCheckingMode": "strict",
|
||||
"python.analysis.extraPaths": [
|
||||
"/opt/netbox/",
|
||||
"/opt/netbox/netbox"
|
||||
],
|
||||
"python.autoComplete.extraPaths": [
|
||||
"/opt/netbox/",
|
||||
"/opt/netbox/netbox"
|
||||
"/opt/netbox/netbox/",
|
||||
"/opt/netbox/netbox/**",
|
||||
"/opt/netbox/netbox/**/**"
|
||||
|
||||
],
|
||||
"python.defaultInterpreterPath": "/opt/netbox/venv/bin/python",
|
||||
"python.formatting.autopep8Path": "/opt/netbox/venv/lib/python3.9/site-packages/autopep8",
|
||||
"python.formatting.blackPath": "/opt/netbox/venv/lib/python3.9/site-packages/black",
|
||||
"python.defaultInterpreterPath": "/opt/netbox/venv/bin/python3",
|
||||
"python.formatting.autopep8Path": "/opt/netbox/venv/bin/autopep8",
|
||||
"python.formatting.blackPath": "/opt/netbox/venv/bin/black",
|
||||
"python.formatting.provider": "black",
|
||||
"python.formatting.yapfPath": "/opt/netbox/venv/lib/python3.9/site-packages/yapf",
|
||||
"python.linting.banditPath": "/opt/netbox/venv/lib/python3.9/site-packages/bandit",
|
||||
"python.formatting.yapfPath": "/opt/netbox/venv/bin/yapf",
|
||||
"python.linting.banditPath": "/opt/netbox/venv/bin/bandit",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.flake8Path": "/opt/netbox/venv/lib/python3.9/site-packages/flake8",
|
||||
"python.linting.mypyPath": "/opt/netbox/venv/lib/python3.9/site-packages/mypy",
|
||||
"python.linting.pycodestylePath": "/opt/netbox/venv/lib/python3.9/site-packages/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/opt/netbox/venv/lib/python3.9/site-packages/pydocstyle",
|
||||
"python.linting.flake8Path": "/opt/netbox/venv/bin/flake8",
|
||||
"python.linting.mypyPath": "//opt/netbox/venv/bin/mypy",
|
||||
"python.linting.pycodestylePath": "/opt/netbox/venv/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/opt/netbox/venv/bin/pydocstyle",
|
||||
"python.linting.pylintArgs": [
|
||||
"--load-plugins",
|
||||
"pylint_django",
|
||||
"--errors-only",
|
||||
"--load-plugins=pylint_django",
|
||||
"--django-settings-module=/opt/netbox/netbox/netbox/netbox.settings",
|
||||
"--enable=W0602,W0611,W0612,W0613,W0614"
|
||||
],
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.pylintPath": "/opt/netbox/venv/lib/python3.9/site-packages/pylint",
|
||||
"python.pythonPath": "/opt/netbox/venv/bin/python",
|
||||
"python.linting.pylintPath": "/opt/netbox/venv/bin/pylint",
|
||||
"python.pythonPath": "/opt/netbox/venv/bin/python3",
|
||||
"python.terminal.activateEnvironment": true,
|
||||
"python.venvPath": "/opt/netbox/"
|
||||
},
|
||||
@ -65,7 +75,11 @@
|
||||
"aaron-bond.better-comments",
|
||||
"GitHub.codespaces",
|
||||
"codezombiech.gitignore",
|
||||
"Tyriar.sort-lines"
|
||||
"Tyriar.sort-lines",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"sourcery.sourcery",
|
||||
"mintlify.document",
|
||||
"batisteo.vscode-django"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@ -27,3 +27,11 @@
|
||||
status: active
|
||||
tenant: tenant2
|
||||
vlan: vlan2
|
||||
- description: prefix3
|
||||
prefix: 192.168.1.0/24
|
||||
site: AMS 1
|
||||
status: active
|
||||
- description: prefix4
|
||||
prefix: 192.168.11.0/24
|
||||
site: AMS 2
|
||||
status: active
|
||||
|
||||
@ -8,4 +8,5 @@ pre-commit
|
||||
pycodestyle
|
||||
pydocstyle
|
||||
pylint
|
||||
pylint-django
|
||||
yapf
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: 📖 Documentation Change
|
||||
description: Suggest an addition or modification to the NetBox access-list plugin documentation
|
||||
description: Suggest an addition or modification to the NetBox Access Lists plugin documentation
|
||||
title: "[Docs]: "
|
||||
labels: ["documentation"]
|
||||
body:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# netbox-access-lists
|
||||
|
||||
A NetBox plugin for Access-List management
|
||||
A NetBox plugin for Access List management
|
||||
|
||||
## Origin
|
||||
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
"""
|
||||
Define the NetBox Plugin
|
||||
"""
|
||||
|
||||
from extras.plugins import PluginConfig
|
||||
|
||||
|
||||
class NetBoxAccessListsConfig(PluginConfig):
|
||||
name = 'netbox_access_lists'
|
||||
verbose_name = ' NetBox Access Lists'
|
||||
verbose_name = 'Access Lists'
|
||||
description = 'Manage simple ACLs in NetBox'
|
||||
version = '0.1'
|
||||
base_url = 'access-lists'
|
||||
|
||||
|
||||
config = NetBoxAccessListsConfig
|
||||
|
||||
@ -1,40 +1,77 @@
|
||||
"""
|
||||
Serializers control the translation of client data to and from Python objects,
|
||||
while Django itself handles the database abstraction.
|
||||
"""
|
||||
|
||||
from dcim.api.nested_serializers import NestedDeviceSerializer
|
||||
from ipam.api.serializers import NestedPrefixSerializer
|
||||
from netbox.api.serializers import (NetBoxModelSerializer,
|
||||
WritableNestedSerializer)
|
||||
from rest_framework import serializers
|
||||
|
||||
from ipam.api.serializers import NestedPrefixSerializer
|
||||
from dcim.api.serializers import NestedDeviceSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
||||
from ..models import AccessList, AccessListRule
|
||||
|
||||
from ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
|
||||
#
|
||||
# Nested serializers
|
||||
#
|
||||
|
||||
|
||||
class NestedAccessListSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
Defines the nested serializer for the django AccessList model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:accesslist-detail'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLStandardRule & fields to the nested serializer.
|
||||
"""
|
||||
model = AccessList
|
||||
fields = ('id', 'url', 'display', 'name', 'device')
|
||||
|
||||
|
||||
class NestedAccessListRuleSerializer(WritableNestedSerializer):
|
||||
class NestedACLStandardRuleSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
Defines the nested serializer for the django ACLStandardRule model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:accesslistrule-detail'
|
||||
view_name='plugins-api:netbox_access_lists-api:aclstandardrule-detail'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AccessListRule
|
||||
"""
|
||||
Associates the django model ACLStandardRule & fields to the nested serializer.
|
||||
"""
|
||||
model = ACLStandardRule
|
||||
fields = ('id', 'url', 'display', 'index')
|
||||
|
||||
|
||||
class NestedACLExtendedRuleSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
Defines the nested serializer for the django ACLExtendedRule model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:aclextendedrule-detail'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLExtendedRule & fields to the nested serializer.
|
||||
"""
|
||||
model = ACLExtendedRule
|
||||
fields = ('id', 'url', 'display', 'index')
|
||||
|
||||
#
|
||||
# Regular serializers
|
||||
#
|
||||
|
||||
|
||||
class AccessListSerializer(NetBoxModelSerializer):
|
||||
"""
|
||||
Defines the serializer for the django AccessList model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:accesslist-detail'
|
||||
)
|
||||
@ -42,25 +79,90 @@ class AccessListSerializer(NetBoxModelSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model AccessList & fields to the serializer.
|
||||
"""
|
||||
model = AccessList
|
||||
fields = (
|
||||
'id', 'url', 'display', 'name', 'device', 'type', 'default_action', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'rule_count',
|
||||
'last_updated', 'rule_count'
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validate the AccessList django model model's inputs before allowing it to update the instance.
|
||||
"""
|
||||
if self.instance.rule_count > 0:
|
||||
raise serializers.ValidationError({
|
||||
'type': 'This ACL has ACL rules already associated, CANNOT change ACL type!!'
|
||||
})
|
||||
|
||||
class AccessListRuleSerializer(NetBoxModelSerializer):
|
||||
return super().validate(data)
|
||||
|
||||
|
||||
class ACLStandardRuleSerializer(NetBoxModelSerializer):
|
||||
"""
|
||||
Defines the serializer for the django ACLStandardRule model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:accesslistrule-detail'
|
||||
view_name='plugins-api:netbox_access_lists-api:aclstandardrule-detail'
|
||||
)
|
||||
access_list = NestedAccessListSerializer()
|
||||
source_prefix = NestedPrefixSerializer()
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLStandardRule & fields to the serializer.
|
||||
"""
|
||||
model = ACLStandardRule
|
||||
fields = (
|
||||
'id', 'url', 'display', 'access_list', 'index', 'action', 'tags', 'description',
|
||||
'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.
|
||||
"""
|
||||
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!!'
|
||||
})
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
|
||||
class ACLExtendedRuleSerializer(NetBoxModelSerializer):
|
||||
"""
|
||||
Defines the serializer for the django ACLExtendedRule model & associates it to a view.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='plugins-api:netbox_access_lists-api:aclextendedrule-detail'
|
||||
)
|
||||
access_list = NestedAccessListSerializer()
|
||||
source_prefix = NestedPrefixSerializer()
|
||||
destination_prefix = NestedPrefixSerializer()
|
||||
|
||||
class Meta:
|
||||
model = AccessListRule
|
||||
"""
|
||||
Associates the django model ACLExtendedRule & fields to the serializer.
|
||||
"""
|
||||
model = ACLExtendedRule
|
||||
fields = (
|
||||
'id', 'url', 'display', 'access_list', 'index', 'protocol', 'source_prefix', 'source_ports',
|
||||
'destination_prefix', 'destination_ports', 'action', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
'id', 'url', 'display', 'access_list', 'index', 'action', 'tags', 'description',
|
||||
'created', 'custom_fields', 'last_updated', 'source_prefix', 'source_ports',
|
||||
'destination_prefix', 'destination_ports', 'protocol'
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validate the ACLExtendedRule django model model's inputs before allowing it to update the instance.
|
||||
"""
|
||||
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!!'
|
||||
})
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
from netbox.api.routers import NetBoxRouter
|
||||
from . import views
|
||||
"""
|
||||
Creates API endpoint URLs for the plugin.
|
||||
"""
|
||||
|
||||
from netbox.api.routers import NetBoxRouter
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'netbox_access_list'
|
||||
|
||||
router = NetBoxRouter()
|
||||
router.register('access-lists', views.AccessListViewSet)
|
||||
router.register('access-list-rules', views.AccessListRuleViewSet)
|
||||
router.register('standard-acl-rules', views.ACLStandardRuleViewSet)
|
||||
router.register('extended-acl-rules', views.ACLExtendedRuleViewSet)
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
@ -1,24 +1,47 @@
|
||||
from django.db.models import Count
|
||||
"""
|
||||
Create views to handle the API logic.
|
||||
A view set is a single class that can handle the view, add, change,
|
||||
and delete operations which each require dedicated views under the UI.
|
||||
"""
|
||||
|
||||
from django.db.models import Count
|
||||
from netbox.api.viewsets import NetBoxModelViewSet
|
||||
|
||||
from .. import filtersets, models
|
||||
from .serializers import AccessListSerializer, AccessListRuleSerializer
|
||||
from .serializers import (AccessListSerializer, ACLExtendedRuleSerializer,
|
||||
ACLStandardRuleSerializer)
|
||||
|
||||
|
||||
class AccessListViewSet(NetBoxModelViewSet):
|
||||
"""
|
||||
Defines the view set for the django AccessList model & associates it to a view.
|
||||
"""
|
||||
queryset = models.AccessList.objects.prefetch_related(
|
||||
'device', 'tags'
|
||||
).annotate(
|
||||
rule_count=Count('rules')
|
||||
rule_count=Count('aclextendedrules') + Count('aclstandardrules')
|
||||
)
|
||||
serializer_class = AccessListSerializer
|
||||
filterset_class = filtersets.AccessListFilterSet
|
||||
|
||||
|
||||
class AccessListRuleViewSet(NetBoxModelViewSet):
|
||||
queryset = models.AccessListRule.objects.prefetch_related(
|
||||
'access_list', 'source_prefix', 'destination_prefix', 'tags'
|
||||
class ACLStandardRuleViewSet(NetBoxModelViewSet):
|
||||
"""
|
||||
Defines the view set for the django ACLStandardRule model & associates it to a view.
|
||||
"""
|
||||
queryset = models.ACLStandardRule.objects.prefetch_related(
|
||||
'access_list', 'tags', 'source_prefix'
|
||||
)
|
||||
serializer_class = AccessListRuleSerializer
|
||||
filterset_class = filtersets.AccessListRuleFilterSet
|
||||
serializer_class = ACLStandardRuleSerializer
|
||||
filterset_class = filtersets.ACLStandardRuleFilterSet
|
||||
|
||||
|
||||
class ACLExtendedRuleViewSet(NetBoxModelViewSet):
|
||||
"""
|
||||
Defines the view set for the django ACLExtendedRule model & associates it to a view.
|
||||
"""
|
||||
queryset = models.ACLExtendedRule.objects.prefetch_related(
|
||||
'access_list', 'tags', 'source_prefix', 'destination_prefix',
|
||||
)
|
||||
serializer_class = ACLExtendedRuleSerializer
|
||||
filterset_class = filtersets.ACLExtendedRuleFilterSet
|
||||
|
||||
63
netbox_access_lists/choices.py
Normal file
63
netbox_access_lists/choices.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""
|
||||
Defines the various choices to be used by the models, forms, and other plugin specifics.
|
||||
"""
|
||||
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
__all__ = (
|
||||
'ACLActionChoices',
|
||||
'ACLRuleActionChoices',
|
||||
'ACLTypeChoices',
|
||||
'ACLProtocolChoices',
|
||||
)
|
||||
|
||||
|
||||
class ACLActionChoices(ChoiceSet):
|
||||
"""
|
||||
Defines the choices availble for the Access Lists plugin specific to ACL default_action.
|
||||
"""
|
||||
ACTION_DENY = 'deny'
|
||||
ACTION_PERMIT = 'permit'
|
||||
ACTION_REJECT = 'reject'
|
||||
|
||||
CHOICES = [
|
||||
(ACTION_DENY, 'Deny', 'red'),
|
||||
(ACTION_PERMIT, 'Permit', 'green'),
|
||||
(ACTION_REJECT, 'Reject (Reset)', 'orange'),
|
||||
]
|
||||
|
||||
|
||||
class ACLRuleActionChoices(ChoiceSet):
|
||||
"""
|
||||
Defines the choices availble for the Access Lists plugin specific to ACL rule actions.
|
||||
"""
|
||||
ACTION_DENY = 'deny'
|
||||
ACTION_PERMIT = 'permit'
|
||||
ACTION_REMARK = 'remark'
|
||||
|
||||
CHOICES = [
|
||||
(ACTION_DENY, 'Deny', 'red'),
|
||||
(ACTION_PERMIT, 'Permit', 'green'),
|
||||
(ACTION_REMARK, 'Remark', 'blue'),
|
||||
]
|
||||
|
||||
|
||||
class ACLTypeChoices(ChoiceSet):
|
||||
"""
|
||||
Defines the choices availble for the Access Lists plugin specific to ACL type.
|
||||
"""
|
||||
CHOICES = [
|
||||
('extended', 'Extended', 'purple'),
|
||||
('standard', 'Standard', 'blue'),
|
||||
]
|
||||
|
||||
|
||||
class ACLProtocolChoices(ChoiceSet):
|
||||
"""
|
||||
Defines the choices availble for the Access Lists plugin specific to ACL Rule protocol.
|
||||
"""
|
||||
CHOICES = [
|
||||
('icmp', 'ICMP', 'purple'),
|
||||
('tcp', 'TCP', 'blue'),
|
||||
('udp', 'UDP', 'orange'),
|
||||
]
|
||||
@ -1,21 +1,71 @@
|
||||
"""
|
||||
Filters enable users to request only a specific subset of objects matching a query;
|
||||
when filtering the sites list by status or region, for instance.
|
||||
"""
|
||||
|
||||
from netbox.filtersets import NetBoxModelFilterSet
|
||||
from .models import AccessList, AccessListRule
|
||||
|
||||
from .models import *
|
||||
|
||||
__all__ = (
|
||||
'AccessListFilterSet',
|
||||
'ACLStandardRuleFilterSet',
|
||||
'ACLExtendedRuleFilterSet',
|
||||
)
|
||||
|
||||
|
||||
class AccessListFilterSet(NetBoxModelFilterSet):
|
||||
"""
|
||||
Define the filter set for the django model AccessList.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model AccessList & fields to the filter set.
|
||||
"""
|
||||
model = AccessList
|
||||
fields = ('id', 'name', 'device', 'type', 'default_action', 'comments')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
"""
|
||||
Override the default search behavior for the django model.
|
||||
"""
|
||||
return queryset.filter(description__icontains=value)
|
||||
|
||||
|
||||
class AccessListRuleFilterSet(NetBoxModelFilterSet):
|
||||
class ACLStandardRuleFilterSet(NetBoxModelFilterSet):
|
||||
"""
|
||||
Define the filter set for the django model ACLStandardRule.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = AccessListRule
|
||||
fields = ('id', 'access_list', 'index', 'protocol', 'action', 'remark')
|
||||
"""
|
||||
Associates the django model ACLStandardRule & fields to the filter set.
|
||||
"""
|
||||
model = ACLStandardRule
|
||||
fields = ('id', 'access_list', 'index', 'action')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
"""
|
||||
Override the default search behavior for the django model.
|
||||
"""
|
||||
return queryset.filter(description__icontains=value)
|
||||
|
||||
|
||||
class ACLExtendedRuleFilterSet(NetBoxModelFilterSet):
|
||||
"""
|
||||
Define the filter set for the django model ACLExtendedRule.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the django model ACLExtendedRule & fields to the filter set.
|
||||
"""
|
||||
model = ACLExtendedRule
|
||||
fields = ('id', 'access_list', 'index', 'action', 'protocol')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
"""
|
||||
Override the default search behavior for the django model.
|
||||
"""
|
||||
return queryset.filter(description__icontains=value)
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
from django import forms
|
||||
|
||||
from extras.models import Tag
|
||||
from ipam.models import Prefix
|
||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm
|
||||
from utilities.forms import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
|
||||
from .models import AccessList, AccessListRule, AccessListActionChoices, AccessListProtocolChoices, AccessListTypeChoices
|
||||
|
||||
|
||||
class AccessListForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AccessList
|
||||
fields = ('name', 'device', 'type', 'default_action', 'comments', 'tags')
|
||||
|
||||
|
||||
class AccessListFilterForm(NetBoxModelFilterSetForm):
|
||||
model = AccessList
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=AccessListTypeChoices,
|
||||
required=False,
|
||||
widget=StaticSelectMultiple()
|
||||
)
|
||||
default_action = forms.MultipleChoiceField(
|
||||
choices=AccessListActionChoices,
|
||||
required=False,
|
||||
widget=StaticSelectMultiple()
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class AccessListRuleForm(NetBoxModelForm):
|
||||
access_list = DynamicModelChoiceField(
|
||||
queryset=AccessList.objects.all()
|
||||
)
|
||||
source_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all()
|
||||
)
|
||||
destination_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all()
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AccessListRule
|
||||
fields = (
|
||||
'access_list', 'index', 'remark', 'source_prefix', 'source_ports', 'destination_prefix',
|
||||
'destination_ports', 'protocol', 'action', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class AccessListRuleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = AccessListRule
|
||||
access_list = forms.ModelMultipleChoiceField(
|
||||
queryset=AccessList.objects.all(),
|
||||
required=False,
|
||||
widget=StaticSelectMultiple()
|
||||
)
|
||||
index = forms.IntegerField(
|
||||
required=False
|
||||
)
|
||||
protocol = forms.MultipleChoiceField(
|
||||
choices=AccessListProtocolChoices,
|
||||
required=False,
|
||||
widget=StaticSelectMultiple()
|
||||
)
|
||||
action = forms.MultipleChoiceField(
|
||||
choices=AccessListActionChoices,
|
||||
required=False,
|
||||
widget=StaticSelectMultiple()
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
13
netbox_access_lists/forms/__init__.py
Normal file
13
netbox_access_lists/forms/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""
|
||||
Import each of the directory's scripts.
|
||||
"""
|
||||
|
||||
#from .bulk_create import *
|
||||
from .bulk_edit import *
|
||||
#from .bulk_import import *
|
||||
#from .connections import *
|
||||
from .filtersets import *
|
||||
#from .formsets import *
|
||||
from .models import *
|
||||
#from .object_create import *
|
||||
#from .object_import import *
|
||||
86
netbox_access_lists/forms/bulk_edit.py
Normal file
86
netbox_access_lists/forms/bulk_edit.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""
|
||||
Draft for a possible BulkEditForm, but may not be worth wile.
|
||||
"""
|
||||
|
||||
from dcim.models import Device, Region, Site, SiteGroup
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.safestring import mark_safe
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from utilities.forms import (ChoiceField, DynamicModelChoiceField,
|
||||
StaticSelect, add_blank_choice)
|
||||
|
||||
from ..choices import ACLActionChoices, ACLTypeChoices
|
||||
from ..models import AccessList
|
||||
|
||||
#__all__ = (
|
||||
# 'AccessListBulkEditForm',
|
||||
#)
|
||||
|
||||
|
||||
#class AccessListBulkEditForm(NetBoxModelBulkEditForm):
|
||||
# model = AccessList
|
||||
#
|
||||
# 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,
|
||||
# )
|
||||
# type = ChoiceField(
|
||||
# choices=add_blank_choice(ACLTypeChoices),
|
||||
# required=False,
|
||||
# widget=StaticSelect(),
|
||||
# )
|
||||
# default_action = ChoiceField(
|
||||
# choices=add_blank_choice(ACLActionChoices),
|
||||
# required=False,
|
||||
# widget=StaticSelect(),
|
||||
# label='Default Action',
|
||||
# )
|
||||
#
|
||||
# fieldsets = [
|
||||
# ('Host Details', ('region', 'site_group', 'site', 'device')),
|
||||
# ('Access List Details', ('type', 'default_action', 'add_tags', 'remove_tags')),
|
||||
# ]
|
||||
#
|
||||
#
|
||||
# class Meta:
|
||||
# model = AccessList
|
||||
# fields = ('region', 'site_group', 'site', 'device', 'type', 'default_action', 'add_tags', 'remove_tags')
|
||||
# help_texts = {
|
||||
# 'default_action': 'The default behavior of the ACL.',
|
||||
# 'name': 'The name uniqueness per device is case insensitive.',
|
||||
# 'type': mark_safe('<b>*Note:</b> CANNOT be changed if ACL Rules are assoicated to this Access List.'),
|
||||
# }
|
||||
#
|
||||
# def clean(self): # Not working given you are bulkd editing multiple forms
|
||||
# cleaned_data = super().clean()
|
||||
# if self.errors.get('name'):
|
||||
# return cleaned_data
|
||||
# name = cleaned_data.get('name')
|
||||
# device = cleaned_data.get('device')
|
||||
# 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():
|
||||
# raise forms.ValidationError('This ACL has Standard ACL rules already associated, CANNOT change ACL type!!')
|
||||
# elif type == 'standard' and self.cleaned_data['aclextendedrules'].exists():
|
||||
# raise forms.ValidationError('This ACL has Extended ACL rules already associated, CANNOT change ACL type!!')
|
||||
# return cleaned_data
|
||||
134
netbox_access_lists/forms/filtersets.py
Normal file
134
netbox_access_lists/forms/filtersets.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""
|
||||
Defines each django model's GUI filter/search options.
|
||||
"""
|
||||
|
||||
from dcim.models import Device, Region, Site, SiteGroup
|
||||
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 ..choices import (ACLActionChoices, ACLProtocolChoices,
|
||||
ACLRuleActionChoices, ACLTypeChoices)
|
||||
from ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
|
||||
__all__ = (
|
||||
'AccessListFilterForm',
|
||||
'ACLStandardRuleFilterForm',
|
||||
'ACLExtendedRuleFilterForm',
|
||||
)
|
||||
|
||||
|
||||
class AccessListFilterForm(NetBoxModelFilterSetForm):
|
||||
"""
|
||||
GUI filter form to search the django AccessList model.
|
||||
"""
|
||||
model = AccessList
|
||||
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
|
||||
)
|
||||
type = ChoiceField(
|
||||
choices=add_blank_choice(ACLTypeChoices),
|
||||
required=False,
|
||||
initial='',
|
||||
widget=StaticSelect(),
|
||||
)
|
||||
default_action = ChoiceField(
|
||||
choices=add_blank_choice(ACLActionChoices),
|
||||
required=False,
|
||||
initial='',
|
||||
widget=StaticSelect(),
|
||||
label='Default Action',
|
||||
)
|
||||
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.
|
||||
"""
|
||||
model = ACLStandardRule
|
||||
tag = TagFilterField(model)
|
||||
source_prefix = forms.ModelMultipleChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
widget=StaticSelectMultiple(),
|
||||
label='Source Prefix',
|
||||
)
|
||||
action = forms.ChoiceField(
|
||||
choices=add_blank_choice(ACLRuleActionChoices),
|
||||
required=False,
|
||||
initial='',
|
||||
widget=StaticSelect(),
|
||||
)
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Rule Details', ('action', 'source_prefix',)),
|
||||
)
|
||||
|
||||
|
||||
class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm):
|
||||
"""
|
||||
GUI filter form to search the django ACLExtendedRule model.
|
||||
"""
|
||||
model = ACLExtendedRule
|
||||
index = forms.IntegerField(
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
action = forms.ChoiceField(
|
||||
choices=add_blank_choice(ACLRuleActionChoices),
|
||||
required=False,
|
||||
widget=StaticSelect(),
|
||||
initial='',
|
||||
)
|
||||
source_prefix = forms.ModelMultipleChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
widget=StaticSelectMultiple(),
|
||||
label='Source Prefix',
|
||||
)
|
||||
desintation_prefix = forms.ModelMultipleChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
widget=StaticSelectMultiple(),
|
||||
label='Destination Prefix',
|
||||
)
|
||||
protocol = ChoiceField(
|
||||
choices=add_blank_choice(ACLProtocolChoices),
|
||||
required=False,
|
||||
widget=StaticSelect(),
|
||||
initial='',
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Rule Details', ('action', 'source_prefix', 'desintation_prefix', 'protocol')),
|
||||
)
|
||||
229
netbox_access_lists/forms/models.py
Normal file
229
netbox_access_lists/forms/models.py
Normal file
@ -0,0 +1,229 @@
|
||||
"""
|
||||
Defines each django model's GUI form to add or edit objects for each django model.
|
||||
"""
|
||||
|
||||
from dcim.models import Device, Region, Site, SiteGroup
|
||||
from django import forms
|
||||
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 ..models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
|
||||
__all__ = (
|
||||
'AccessListForm',
|
||||
'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.')
|
||||
|
||||
|
||||
class AccessListForm(NetBoxModelForm):
|
||||
"""
|
||||
GUI form to add or edit an AccessList.
|
||||
Requires a device, a name, a type, and a default_action.
|
||||
"""
|
||||
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',
|
||||
},
|
||||
)
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = [
|
||||
('Host Details', ('region', 'site_group', 'site', 'device')),
|
||||
('Access List Details', ('name', 'type', 'default_action', 'tags')),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
model = AccessList
|
||||
fields = ('region', 'site_group', 'site', 'device', 'name', 'type', 'default_action', 'comments', 'tags')
|
||||
help_texts = {
|
||||
'default_action': 'The default behavior of the ACL.',
|
||||
'name': 'The name uniqueness per device is case insensitive.',
|
||||
'type': mark_safe('<b>*Note:</b> CANNOT be changed if ACL Rules are assoicated to this Access List.'),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Validates form inputs before submitting.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
if self.errors.get('name'):
|
||||
return cleaned_data
|
||||
type = cleaned_data.get('type')
|
||||
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:
|
||||
raise forms.ValidationError(error_message)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class ACLStandardRuleForm(NetBoxModelForm):
|
||||
"""
|
||||
GUI form to add or edit Standard Access List.
|
||||
Requires an access_list, an index, and ACL rule type.
|
||||
See the clean function for logic on other field requirements.
|
||||
"""
|
||||
access_list = DynamicModelChoiceField(
|
||||
queryset=AccessList.objects.all(),
|
||||
query_params={
|
||||
'type': 'standard'
|
||||
},
|
||||
help_text=mark_safe('<b>*Note:</b> This field will only display Standard ACLs.'),
|
||||
label='Access List',
|
||||
)
|
||||
source_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
help_text=acl_rule_logic_help,
|
||||
label='Source Prefix',
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Access List Details', ('access_list', 'description', 'tags')),
|
||||
('Rule Definition', ('index', 'action', 'remark', 'source_prefix')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ACLStandardRule
|
||||
fields = (
|
||||
'access_list', 'index', 'action', 'remark', 'source_prefix',
|
||||
'tags', 'description'
|
||||
)
|
||||
help_texts = {
|
||||
'index': 'Determines the order of the rule in the ACL processing.',
|
||||
'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.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
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.']})
|
||||
if cleaned_data.get('source_prefix'):
|
||||
error_message.update({'source_prefix': ['Action is set to remark, Source Prefix CANNOT be set.']})
|
||||
elif cleaned_data.get('remark'):
|
||||
error_message.update({'remark': ['CANNOT set remark unless action is set to remark, .']})
|
||||
if len(error_message) > 0:
|
||||
raise forms.ValidationError(error_message)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class ACLExtendedRuleForm(NetBoxModelForm):
|
||||
"""
|
||||
GUI form to add or edit Extended Access List.
|
||||
Requires an access_list, an index, and ACL rule type.
|
||||
See the clean function for logic on other field requirements.
|
||||
"""
|
||||
access_list = DynamicModelChoiceField(
|
||||
queryset=AccessList.objects.all(),
|
||||
query_params={
|
||||
'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,
|
||||
help_text=acl_rule_logic_help,
|
||||
label='Source Prefix',
|
||||
)
|
||||
destination_prefix = DynamicModelChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
required=False,
|
||||
help_text=acl_rule_logic_help,
|
||||
label='Destination Prefix',
|
||||
)
|
||||
fieldsets = (
|
||||
('Access List Details', ('access_list', 'description', 'tags')),
|
||||
('Rule Definition', ('index', 'action', 'remark', 'source_prefix', 'source_ports', 'destination_prefix', 'destination_ports', 'protocol',)),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ACLExtendedRule
|
||||
fields = (
|
||||
'access_list', 'index', 'action', 'remark', 'source_prefix',
|
||||
'source_ports', 'destination_prefix', 'destination_ports', 'protocol',
|
||||
'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,
|
||||
'remark': mark_safe('<b>*Note:</b> CANNOT be set if action is not set to remark.'),
|
||||
'source_ports': acl_rule_logic_help,
|
||||
}
|
||||
|
||||
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.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
error_message = {}
|
||||
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.']})
|
||||
if cleaned_data.get('source_prefix'):
|
||||
error_message.update({'source_prefix': ['Action is set to remark, Source Prefix CANNOT be set.']})
|
||||
if cleaned_data.get('source_ports'):
|
||||
error_message.update({'source_ports': ['Action is set to remark, Source Ports CANNOT be set.']})
|
||||
if cleaned_data.get('destination_prefix'):
|
||||
error_message.update({'destination_prefix': ['Action is set to remark, Destination Prefix CANNOT be set.']})
|
||||
if cleaned_data.get('destination_ports'):
|
||||
error_message.update({'destination_ports': ['Action is set to remark, Destination Ports CANNOT be set.']})
|
||||
if cleaned_data.get('protocol'):
|
||||
error_message.update({'protocol': ['Action is set to remark, Protocol CANNOT be set.']})
|
||||
elif cleaned_data.get('remark'):
|
||||
error_message.update({'remark': ['CANNOT set remark unless action is set to remark, .']})
|
||||
if len(error_message) > 0:
|
||||
raise forms.ValidationError(error_message)
|
||||
return cleaned_data
|
||||
@ -1,39 +1,85 @@
|
||||
"""
|
||||
Define the object types and queries availble via the graphql api.
|
||||
"""
|
||||
|
||||
from graphene import ObjectType
|
||||
from netbox.graphql.types import NetBoxObjectType
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from netbox.graphql.types import NetBoxObjectType
|
||||
|
||||
from . import filtersets, models
|
||||
|
||||
__all__ = (
|
||||
'AccessListType',
|
||||
'ACLExtendedRuleType',
|
||||
'ACLStandardRuleType',
|
||||
)
|
||||
|
||||
#
|
||||
# Object types
|
||||
#
|
||||
|
||||
|
||||
class AccessListType(NetBoxObjectType):
|
||||
"""
|
||||
Defines the object type for the django model AccessList.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the filterset, fields, and model for the django model AccessList.
|
||||
"""
|
||||
model = models.AccessList
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.AccessListFilterSet
|
||||
|
||||
|
||||
class AccessListRuleType(NetBoxObjectType):
|
||||
class ACLExtendedRuleType(NetBoxObjectType):
|
||||
"""
|
||||
Defines the object type for the django model ACLExtendedRule.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = models.AccessListRule
|
||||
"""
|
||||
Associates the filterset, fields, and model for the django model ACLExtendedRule.
|
||||
"""
|
||||
model = models.ACLExtendedRule
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.AccessListRuleFilterSet
|
||||
filterset_class = filtersets.ACLExtendedRuleFilterSet
|
||||
|
||||
|
||||
class ACLStandardRuleType(NetBoxObjectType):
|
||||
"""
|
||||
Defines the object type for the django model ACLStandardRule.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Associates the filterset, fields, and model for the django model ACLStandardRule.
|
||||
"""
|
||||
model = models.ACLStandardRule
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ACLStandardRuleFilterSet
|
||||
|
||||
#
|
||||
# Queries
|
||||
#
|
||||
|
||||
|
||||
class Query(ObjectType):
|
||||
"""
|
||||
Defines the queries availible to this plugin via the graphql api.
|
||||
"""
|
||||
|
||||
access_list = ObjectField(AccessListType)
|
||||
access_lists = ObjectListField(AccessListType)
|
||||
access_list_list = ObjectListField(AccessListType)
|
||||
|
||||
access_list_rule = ObjectField(AccessListRuleType)
|
||||
access_list_rule_list = ObjectListField(AccessListRuleType)
|
||||
acl_extended_rule = ObjectField(ACLExtendedRuleType)
|
||||
acl_extended_rules = ObjectListField(ACLExtendedRuleType)
|
||||
acl_extended_rule_list = ObjectListField(ACLExtendedRuleType)
|
||||
|
||||
acl_standard_rule = ObjectField(ACLStandardRuleType)
|
||||
acl_standard_rules = ObjectListField(ACLStandardRuleType)
|
||||
acl_standard_rule_list = ObjectListField(ACLStandardRuleType)
|
||||
|
||||
schema = Query
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
"""
|
||||
Defines the migrations for propogating django models into the database schemea.
|
||||
"""
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
__all__ = (
|
||||
'Migration',
|
||||
)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""
|
||||
Defines the migrations required for the initial setup of the access lists plugin and its associated django models.
|
||||
"""
|
||||
|
||||
initial = True
|
||||
|
||||
@ -25,36 +35,60 @@ class Migration(migrations.Migration):
|
||||
('name', models.CharField(max_length=100)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_lists', to='dcim.device')),
|
||||
('type', models.CharField(max_length=100)),
|
||||
('default_action', models.CharField(max_length=30)),
|
||||
('default_action', models.CharField(default='deny', max_length=30)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name', 'device'),
|
||||
'unique_together': {('name', 'device')},
|
||||
'verbose_name': 'Access List',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AccessListRule',
|
||||
name='ACLStandardRule',
|
||||
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)),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
('access_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aclstandardrules', to='netbox_access_lists.accesslist')),
|
||||
('index', models.PositiveIntegerField()),
|
||||
('protocol', models.CharField(blank=True, max_length=30)),
|
||||
('source_ports', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveIntegerField(), blank=True, null=True, size=None)),
|
||||
('destination_ports', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveIntegerField(), blank=True, null=True, size=None)),
|
||||
('description', models.CharField(blank=True, max_length=500)),
|
||||
('action', models.CharField(max_length=30)),
|
||||
('remark', models.CharField(blank=True, null=True, max_length=500)),
|
||||
('access_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='netbox_access_lists.accesslist')),
|
||||
('destination_prefix', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.prefix')),
|
||||
('source_prefix', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.prefix')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('access_list', 'index'),
|
||||
'unique_together': {('access_list', 'index')},
|
||||
'verbose_name': 'ACL Standard Rule',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ACLExtendedRule',
|
||||
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)),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
('access_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aclstandardrules', to='netbox_access_lists.accesslist')),
|
||||
('index', models.PositiveIntegerField()),
|
||||
('description', models.CharField(blank=True, max_length=500)),
|
||||
('action', models.CharField(max_length=30)),
|
||||
('remark', models.CharField(blank=True, null=True, max_length=500)),
|
||||
('source_prefix', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.prefix')),
|
||||
('source_ports', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveIntegerField(), blank=True, null=True, size=None)),
|
||||
('destination_prefix', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.prefix')),
|
||||
('destination_ports', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveIntegerField(), blank=True, null=True, size=None)),
|
||||
('protocol', models.CharField(blank=True, max_length=30)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('access_list', 'index'),
|
||||
'unique_together': {('access_list', 'index')},
|
||||
'verbose_name': 'ACL Extended Rule',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,144 +0,0 @@
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from netbox.models import NetBoxModel
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
|
||||
class AccessListActionChoices(ChoiceSet):
|
||||
key = 'AccessListRule.action'
|
||||
ACTION_DENY = 'deny'
|
||||
ACTION_PERMIT = 'permit'
|
||||
ACTION_REJECT = 'reject'
|
||||
|
||||
CHOICES = [
|
||||
(ACTION_DENY, 'Deny', 'red'),
|
||||
(ACTION_PERMIT, 'Permit', 'green'),
|
||||
(ACTION_REJECT, 'Reject (Reset)', 'orange'),
|
||||
]
|
||||
|
||||
|
||||
class AccessListTypeChoices(ChoiceSet):
|
||||
|
||||
CHOICES = [
|
||||
('extended', 'Extended', 'purple'),
|
||||
('standard', 'Standard', 'blue'),
|
||||
]
|
||||
|
||||
|
||||
class AccessListProtocolChoices(ChoiceSet):
|
||||
|
||||
CHOICES = [
|
||||
('icmp', 'ICMP', 'purple'),
|
||||
('tcp', 'TCP', 'blue'),
|
||||
('udp', 'UDP', 'orange'),
|
||||
]
|
||||
|
||||
|
||||
class AccessList(NetBoxModel):
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='access_lists'
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=30,
|
||||
choices=AccessListTypeChoices
|
||||
)
|
||||
default_action = models.CharField(
|
||||
default=AccessListActionChoices.ACTION_DENY,
|
||||
max_length=30,
|
||||
choices=AccessListActionChoices,
|
||||
verbose_name='Default Action'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', 'device')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('plugins:netbox_access_lists:accesslist', args=[self.pk])
|
||||
|
||||
def get_default_action_color(self):
|
||||
return AccessListActionChoices.colors.get(self.default_action)
|
||||
|
||||
def get_type_color(self):
|
||||
return AccessListTypeChoices.colors.get(self.type)
|
||||
|
||||
|
||||
class AccessListRule(NetBoxModel):
|
||||
access_list = models.ForeignKey(
|
||||
on_delete=models.CASCADE,
|
||||
related_name='rules',
|
||||
to=AccessList,
|
||||
verbose_name='Access List'
|
||||
)
|
||||
index = models.PositiveIntegerField()
|
||||
protocol = models.CharField(
|
||||
blank=True,
|
||||
choices=AccessListProtocolChoices,
|
||||
max_length=30,
|
||||
)
|
||||
source_prefix = models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
to='ipam.Prefix',
|
||||
verbose_name='Source Prefix'
|
||||
)
|
||||
source_ports = ArrayField(
|
||||
base_field=models.PositiveIntegerField(),
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Soure Ports'
|
||||
)
|
||||
destination_prefix = models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
to='ipam.Prefix',
|
||||
verbose_name='Destination Prefix'
|
||||
)
|
||||
destination_ports = ArrayField(
|
||||
base_field=models.PositiveIntegerField(),
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Destination Ports'
|
||||
)
|
||||
action = models.CharField(
|
||||
choices=AccessListActionChoices,
|
||||
default=AccessListActionChoices.ACTION_PERMIT,
|
||||
max_length=30,
|
||||
)
|
||||
remark = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('access_list', 'index')
|
||||
unique_together = ('access_list', 'index')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.access_list}: Rule {self.index}'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('plugins:netbox_access_lists:accesslistrule', args=[self.pk])
|
||||
|
||||
def get_protocol_color(self):
|
||||
return AccessListProtocolChoices.colors.get(self.protocol)
|
||||
|
||||
def get_action_color(self):
|
||||
return AccessListActionChoices.colors.get(self.action)
|
||||
6
netbox_access_lists/models/__init__.py
Normal file
6
netbox_access_lists/models/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""
|
||||
Import each of the directory's scripts.
|
||||
"""
|
||||
|
||||
from .access_list_rules import *
|
||||
from .access_lists import *
|
||||
144
netbox_access_lists/models/access_list_rules.py
Normal file
144
netbox_access_lists/models/access_list_rules.py
Normal file
@ -0,0 +1,144 @@
|
||||
"""
|
||||
Define the django models for this plugin.
|
||||
"""
|
||||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from netbox.models import NetBoxModel
|
||||
|
||||
from ..choices import *
|
||||
from .access_lists import AccessList
|
||||
|
||||
__all__ = (
|
||||
'ACLRule',
|
||||
'ACLStandardRule',
|
||||
'ACLExtendedRule',
|
||||
)
|
||||
|
||||
|
||||
class ACLRule(NetBoxModel):
|
||||
"""
|
||||
Abstract model for ACL Rules.
|
||||
Inherrited by both ACLStandardRule and ACLExtendedRule.
|
||||
"""
|
||||
access_list = models.ForeignKey(
|
||||
on_delete=models.CASCADE,
|
||||
to=AccessList,
|
||||
verbose_name='Access List',
|
||||
)
|
||||
index = models.PositiveIntegerField()
|
||||
remark = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=500,
|
||||
blank=True
|
||||
)
|
||||
action = models.CharField(
|
||||
choices=ACLRuleActionChoices,
|
||||
max_length=30,
|
||||
)
|
||||
source_prefix = models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
to='ipam.Prefix',
|
||||
verbose_name='Source Prefix'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.access_list}: Rule {self.index}'
|
||||
|
||||
def get_action_color(self):
|
||||
return ACLRuleActionChoices.colors.get(self.action)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Define the common model properties:
|
||||
- as an abstract model
|
||||
- ordering
|
||||
- unique together
|
||||
"""
|
||||
abstract = True
|
||||
ordering = ('access_list', 'index')
|
||||
unique_together = ('access_list', 'index')
|
||||
|
||||
|
||||
class ACLStandardRule(ACLRule):
|
||||
"""
|
||||
Inherits ACLRule.
|
||||
"""
|
||||
|
||||
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:aclstandardrule', args=[self.pk])
|
||||
|
||||
class Meta(ACLRule.Meta):
|
||||
"""
|
||||
Define the model properties adding to or overriding the inherited class:
|
||||
- default_related_name for any FK relationships
|
||||
- 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'
|
||||
|
||||
class ACLExtendedRule(ACLRule):
|
||||
"""
|
||||
Inherits ACLRule.
|
||||
Add ACLExtendedRule specific fields: source_ports, desintation_prefix, destination_ports, and protocol
|
||||
"""
|
||||
source_ports = ArrayField(
|
||||
base_field=models.PositiveIntegerField(),
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Soure Ports'
|
||||
)
|
||||
destination_prefix = models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
to='ipam.Prefix',
|
||||
verbose_name='Destination Prefix'
|
||||
)
|
||||
destination_ports = ArrayField(
|
||||
base_field=models.PositiveIntegerField(),
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Destination Ports'
|
||||
)
|
||||
protocol = models.CharField(
|
||||
blank=True,
|
||||
choices=ACLProtocolChoices,
|
||||
max_length=30,
|
||||
)
|
||||
|
||||
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:aclextendedrule', args=[self.pk])
|
||||
|
||||
def get_protocol_color(self):
|
||||
return ACLProtocolChoices.colors.get(self.protocol)
|
||||
|
||||
class Meta(ACLRule.Meta):
|
||||
"""
|
||||
Define the model properties adding to or overriding the inherited class:
|
||||
- default_related_name for any FK relationships
|
||||
- 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'
|
||||
63
netbox_access_lists/models/access_lists.py
Normal file
63
netbox_access_lists/models/access_lists.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""
|
||||
Define the django models for this plugin.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from netbox.models import NetBoxModel
|
||||
|
||||
from ..choices import *
|
||||
|
||||
__all__ = (
|
||||
'AccessList',
|
||||
)
|
||||
|
||||
|
||||
class AccessList(NetBoxModel):
|
||||
"""
|
||||
Model defintion for Access Lists.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='access_lists'
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=30,
|
||||
choices=ACLTypeChoices
|
||||
)
|
||||
default_action = models.CharField(
|
||||
default=ACLActionChoices.ACTION_DENY,
|
||||
max_length=30,
|
||||
choices=ACLActionChoices,
|
||||
verbose_name='Default Action'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', 'device')
|
||||
unique_together = ('name', 'device')
|
||||
default_related_name='accesslists'
|
||||
verbose_name='Access List'
|
||||
verbose_name_plural='Access Lists'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
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:accesslist', args=[self.pk])
|
||||
|
||||
def get_default_action_color(self):
|
||||
return ACLActionChoices.colors.get(self.default_action)
|
||||
|
||||
def get_type_color(self):
|
||||
return ACLTypeChoices.colors.get(self.type)
|
||||
@ -1,6 +1,13 @@
|
||||
"""
|
||||
Define the plugin menu buttons & the plugin navigation bar enteries.
|
||||
"""
|
||||
|
||||
from extras.plugins import PluginMenuButton, PluginMenuItem
|
||||
from utilities.choices import ButtonColorChoices
|
||||
|
||||
#
|
||||
# Define plugin menu buttons
|
||||
#
|
||||
|
||||
accesslist_buttons = [
|
||||
PluginMenuButton(
|
||||
@ -11,25 +18,44 @@ accesslist_buttons = [
|
||||
)
|
||||
]
|
||||
|
||||
accesslistrule_butons = [
|
||||
aclstandardrule_butons = [
|
||||
PluginMenuButton(
|
||||
link='plugins:netbox_access_lists:accesslistrule_add',
|
||||
link='plugins:netbox_access_lists:aclstandardrule_add',
|
||||
title='Add',
|
||||
icon_class='mdi mdi-plus-thick',
|
||||
color=ButtonColorChoices.GREEN
|
||||
)
|
||||
]
|
||||
|
||||
aclextendedrule_butons = [
|
||||
PluginMenuButton(
|
||||
link='plugins:netbox_access_lists:aclextendedrule_add',
|
||||
title='Add',
|
||||
icon_class='mdi mdi-plus-thick',
|
||||
color=ButtonColorChoices.GREEN
|
||||
)
|
||||
]
|
||||
|
||||
#
|
||||
# Define navigation bar links including the above buttons defined.
|
||||
#
|
||||
|
||||
menu_items = (
|
||||
PluginMenuItem(
|
||||
link='plugins:netbox_access_lists:accesslist_list',
|
||||
link_text='Access Lists',
|
||||
buttons=accesslist_buttons
|
||||
),
|
||||
# # Comment out Access List Rule to force creation in the ACL view
|
||||
# PluginMenuItem(
|
||||
# link='plugins:netbox_access_lists:accesslistrule_list',
|
||||
# link_text='Access List Rules',
|
||||
# buttons=accesslistrule_butons
|
||||
# ),
|
||||
# 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',
|
||||
buttons=aclstandardrule_butons
|
||||
),
|
||||
# Comment out Extended Access List rule to force creation in the ACL view
|
||||
PluginMenuItem(
|
||||
link='plugins:netbox_access_lists:aclextendedrule_list',
|
||||
link_text='ACL Extended Rules',
|
||||
buttons=aclextendedrule_butons
|
||||
),
|
||||
)
|
||||
|
||||
@ -1,10 +1,23 @@
|
||||
import django_tables2 as tables
|
||||
"""
|
||||
Define the object lists / table view for each of the plugin models.
|
||||
"""
|
||||
|
||||
from netbox.tables import NetBoxTable, columns, ChoiceFieldColumn
|
||||
from .models import AccessList, AccessListRule
|
||||
import django_tables2 as tables
|
||||
from netbox.tables import ChoiceFieldColumn, NetBoxTable, columns
|
||||
|
||||
from .models import AccessList, ACLExtendedRule, ACLStandardRule
|
||||
|
||||
__all__ = (
|
||||
'AccessListTable',
|
||||
'ACLStandardRuleTable',
|
||||
'ACLExtendedRuleTable',
|
||||
)
|
||||
|
||||
|
||||
class AccessListTable(NetBoxTable):
|
||||
"""
|
||||
Defines the table view for the AccessList model.
|
||||
"""
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
@ -26,26 +39,54 @@ class AccessListTable(NetBoxTable):
|
||||
default_columns = ('name', 'device', 'type', 'rule_count', 'default_action', 'tags')
|
||||
|
||||
|
||||
class AccessListRuleTable(NetBoxTable):
|
||||
class ACLStandardRuleTable(NetBoxTable):
|
||||
"""
|
||||
Defines the table view for the ACLStandardRule model.
|
||||
"""
|
||||
access_list = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
index = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
protocol = ChoiceFieldColumn()
|
||||
action = ChoiceFieldColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='plugins:netbox_access_lists:accesslistrule_list'
|
||||
url_name='plugins:netbox_access_lists:aclstandardrule_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = AccessListRule
|
||||
model = ACLStandardRule
|
||||
fields = (
|
||||
'pk', 'id', 'access_list', 'index', 'source_prefix', 'source_ports', 'destination_prefix',
|
||||
'destination_ports', 'protocol', 'action', 'remark', 'actions', 'tags'
|
||||
'pk', 'id', 'access_list', 'index', 'action', 'actions', 'remark', 'tags', 'description',
|
||||
)
|
||||
default_columns = (
|
||||
'access_list', 'index', 'remark', 'source_prefix', 'source_ports', 'destination_prefix',
|
||||
'destination_ports', 'protocol', 'action', 'actions', 'tags'
|
||||
'access_list', 'index', 'action', 'actions', 'remark', 'tags'
|
||||
)
|
||||
|
||||
|
||||
class ACLExtendedRuleTable(NetBoxTable):
|
||||
"""
|
||||
Defines the table view for the ACLExtendedRule model.
|
||||
"""
|
||||
access_list = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
index = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
action = ChoiceFieldColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='plugins:netbox_access_lists:aclextendedrule_list'
|
||||
)
|
||||
protocol = ChoiceFieldColumn()
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ACLExtendedRule
|
||||
fields = (
|
||||
'pk', 'id', 'access_list', 'index', 'action', 'actions', 'remark', 'tags', 'description',
|
||||
'source_prefix', 'source_ports', 'destination_prefix', 'destination_ports', 'protocol'
|
||||
)
|
||||
default_columns = (
|
||||
'access_list', 'index', 'action', 'actions', 'remark', 'tags',
|
||||
'source_prefix', 'source_ports', 'destination_prefix', 'destination_ports', 'protocol'
|
||||
)
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from extras.plugins import PluginTemplateExtension
|
||||
|
||||
from .models import AccessList
|
||||
|
||||
__all__ = (
|
||||
'AccessLists',
|
||||
'DeviceAccessLists',
|
||||
)
|
||||
|
||||
|
||||
class AccessLists(PluginTemplateExtension):
|
||||
|
||||
@ -17,7 +22,7 @@ class AccessLists(PluginTemplateExtension):
|
||||
#elif ctype.model == 'virtualmachine':
|
||||
# access_lists = AccessList.objects.filter(device=obj.pk)
|
||||
|
||||
return self.render('inc/device_access_lists.html', extra_context={
|
||||
return self.render('inc/device/access_lists.html', extra_context={
|
||||
'access_lists': access_lists,
|
||||
'type': ctype.model if ctype.model == 'device' else ctype.name.replace(' ', '_'),
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
Access Lists
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
{% include 'inc/assigned_access_lists.html' %}
|
||||
{% include 'inc/device/assigned_access_lists.html' %}
|
||||
</div>
|
||||
<div class="card-footer text-end noprint">
|
||||
<a href="{% url 'plugins:netbox_access_lists:accesslist_add' %}?{{ type }}={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">
|
||||
@ -12,7 +12,11 @@
|
||||
<td>{{ access_list|linkify }}</td>
|
||||
<td>{{ access_list.type|title }}</td>
|
||||
<td>{{ access_list.default_action|title }}</td>
|
||||
<td>{{ access_list.rules.count }}</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>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@ -7,7 +7,11 @@
|
||||
{% block controls %}
|
||||
<div class="pull-right noprint">
|
||||
{% if perms.netbox_access_lists.change_policy %}
|
||||
<a href="{% url 'plugins:netbox_access_lists:accesslistrule_add' %}?access_list={{ object.pk }}" class="btn btn-success">
|
||||
{% if object.type == 'extended' %}
|
||||
<a href="{% url 'plugins:netbox_access_lists:aclextendedrule_add' %}?access_list={{ object.pk }}" class="btn btn-success">
|
||||
{% elif object.type == 'standard' %}
|
||||
<a href="{% url 'plugins:netbox_access_lists:aclstandardrule_add' %}?access_list={{ object.pk }}" class="btn btn-success">
|
||||
{% endif %}
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Rule
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -45,10 +49,6 @@
|
||||
<h5 class="card-header">Access List</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Type</th>
|
||||
<td>{{ object.get_type_display }}</td>
|
||||
@ -59,7 +59,15 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Rules</th>
|
||||
<td>{{ object.rules.count }}</td>
|
||||
{% if object.type == 'standard' %}
|
||||
<td>{{ object.aclstandardrules.count|placeholder }}</td>
|
||||
{% elif object.type == 'extended' %}
|
||||
<td>{{ object.aclextendedrules.count|placeholder }}</td>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">Device</th>
|
||||
<td>{{ object.device|linkify }}</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@ -74,7 +82,7 @@
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Rules</h5>
|
||||
<h5 class="card-header">{{ object.get_type_display }} Rules</h5>
|
||||
<div class="card-body table-responsive">
|
||||
{% render_table rules_table %}
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Access List Rule</h5>
|
||||
<h5 class="card-header">ACL Extended Rule</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
@ -13,6 +13,10 @@
|
||||
<a href="{{ object.access_list.get_absolute_url }}">{{ object.access_list }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Index</th>
|
||||
<td>{{ object.index }}</td>
|
||||
@ -0,0 +1,62 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">ACL Standard Rule</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<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">Index</th>
|
||||
<td>{{ object.index }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Details</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Remark</th>
|
||||
<td>{{ object.get_remark_display|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Protocol</th>
|
||||
<td>{{ object.get_protocol_display|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Source Prefix</th>
|
||||
<td>
|
||||
{% if object.source_prefix %}
|
||||
<a href="{{ object.source_prefix.get_absolute_url }}">{{ object.source_prefix }}</a>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Action</th>
|
||||
<td>{{ object.get_action_display }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@ -1,14 +1,18 @@
|
||||
"""
|
||||
Map Views to URLs.
|
||||
"""
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from netbox.views.generic import ObjectChangeLogView
|
||||
from . import models, views
|
||||
|
||||
from . import models, views
|
||||
|
||||
urlpatterns = (
|
||||
|
||||
# Access lists
|
||||
# Access Lists
|
||||
path('access-lists/', views.AccessListListView.as_view(), name='accesslist_list'),
|
||||
path('access-lists/add/', views.AccessListEditView.as_view(), name='accesslist_add'),
|
||||
#path('access-lists/edit/', views.AccessListBulkEditView.as_view(), name='accesslist_bulk_edit'),
|
||||
path('access-lists/<int:pk>/', views.AccessListView.as_view(), name='accesslist'),
|
||||
path('access-lists/<int:pk>/edit/', views.AccessListEditView.as_view(), name='accesslist_edit'),
|
||||
path('access-lists/<int:pk>/delete/', views.AccessListDeleteView.as_view(), name='accesslist_delete'),
|
||||
@ -16,14 +20,24 @@ urlpatterns = (
|
||||
'model': models.AccessList
|
||||
}),
|
||||
|
||||
# Access list rules
|
||||
path('rules/', views.AccessListRuleListView.as_view(), name='accesslistrule_list'),
|
||||
path('rules/add/', views.AccessListRuleEditView.as_view(), name='accesslistrule_add'),
|
||||
path('rules/<int:pk>/', views.AccessListRuleView.as_view(), name='accesslistrule'),
|
||||
path('rules/<int:pk>/edit/', views.AccessListRuleEditView.as_view(), name='accesslistrule_edit'),
|
||||
path('rules/<int:pk>/delete/', views.AccessListRuleDeleteView.as_view(), name='accesslistrule_delete'),
|
||||
path('rules/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='accesslistrule_changelog', kwargs={
|
||||
'model': models.AccessListRule
|
||||
# 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/<int:pk>/', views.ACLStandardRuleView.as_view(), name='aclstandardrule'),
|
||||
path('standard-rules/<int:pk>/edit/', views.ACLStandardRuleEditView.as_view(), name='aclstandardrule_edit'),
|
||||
path('standard-rules/<int:pk>/delete/', views.ACLStandardRuleDeleteView.as_view(), name='aclstandardrule_delete'),
|
||||
path('standard-rules/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aclstandardrule_changelog', kwargs={
|
||||
'model': models.ACLStandardRule
|
||||
}),
|
||||
|
||||
# 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/<int:pk>/', views.ACLExtendedRuleView.as_view(), name='aclextendedrule'),
|
||||
path('extended-rules/<int:pk>/edit/', views.ACLExtendedRuleEditView.as_view(), name='aclextendedrule_edit'),
|
||||
path('extended-rules/<int:pk>/delete/', views.ACLExtendedRuleDeleteView.as_view(), name='aclextendedrule_delete'),
|
||||
path('extended-rules/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aclextendedrule_changelog', kwargs={
|
||||
'model': models.ACLExtendedRule
|
||||
}),
|
||||
|
||||
)
|
||||
|
||||
@ -1 +1 @@
|
||||
__version__ = "0.0.0"
|
||||
__version__ = "1.0.0"
|
||||
|
||||
@ -1,28 +1,59 @@
|
||||
from django.db.models import Count
|
||||
"""
|
||||
Defines the business logic for the plugin.
|
||||
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
|
||||
|
||||
__all__ = (
|
||||
'AccessListView',
|
||||
'AccessListListView',
|
||||
'AccessListEditView',
|
||||
'ACLStandardRuleView',
|
||||
'ACLStandardRuleListView',
|
||||
'ACLStandardRuleEditView',
|
||||
'ACLStandardRuleDeleteView',
|
||||
'ACLExtendedRuleView',
|
||||
'ACLExtendedRuleListView',
|
||||
'ACLExtendedRuleEditView',
|
||||
'ACLExtendedRuleDeleteView',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# AccessList views
|
||||
#
|
||||
|
||||
class AccessListView(generic.ObjectView):
|
||||
"""
|
||||
Defines the view for the AccessLists django model.
|
||||
"""
|
||||
queryset = models.AccessList.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
table = tables.AccessListRuleTable(instance.rules.all())
|
||||
"""
|
||||
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,
|
||||
'rules_table': table
|
||||
}
|
||||
|
||||
|
||||
class AccessListListView(generic.ObjectListView):
|
||||
"""
|
||||
Defines the list view for the AccessLists django model.
|
||||
"""
|
||||
queryset = models.AccessList.objects.annotate(
|
||||
rule_count=Count('rules')
|
||||
rule_count=Count('aclextendedrules') + Count('aclstandardrules')
|
||||
)
|
||||
table = tables.AccessListTable
|
||||
filterset = filtersets.AccessListFilterSet
|
||||
@ -30,33 +61,99 @@ class AccessListListView(generic.ObjectListView):
|
||||
|
||||
|
||||
class AccessListEditView(generic.ObjectEditView):
|
||||
"""
|
||||
Defines the edit view for the AccessLists django model.
|
||||
"""
|
||||
queryset = models.AccessList.objects.all()
|
||||
form = forms.AccessListForm
|
||||
|
||||
|
||||
class AccessListDeleteView(generic.ObjectDeleteView):
|
||||
"""
|
||||
Defines the delete view for the AccessLists django model.
|
||||
"""
|
||||
queryset = models.AccessList.objects.all()
|
||||
|
||||
|
||||
#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
|
||||
|
||||
#
|
||||
# AccessListRule views
|
||||
# ACLStandardRule views
|
||||
#
|
||||
|
||||
class AccessListRuleView(generic.ObjectView):
|
||||
queryset = models.AccessListRule.objects.all()
|
||||
|
||||
class ACLStandardRuleView(generic.ObjectView):
|
||||
"""
|
||||
Defines the view for the ACLStandardRule django model.
|
||||
"""
|
||||
queryset = models.ACLStandardRule.objects.all()
|
||||
|
||||
|
||||
class AccessListRuleListView(generic.ObjectListView):
|
||||
queryset = models.AccessListRule.objects.all()
|
||||
table = tables.AccessListRuleTable
|
||||
filterset = filtersets.AccessListRuleFilterSet
|
||||
filterset_form = forms.AccessListRuleFilterForm
|
||||
class ACLStandardRuleListView(generic.ObjectListView):
|
||||
"""
|
||||
Defines the list view for the ACLStandardRule django model.
|
||||
"""
|
||||
queryset = models.ACLStandardRule.objects.all()
|
||||
table = tables.ACLStandardRuleTable
|
||||
filterset = filtersets.ACLStandardRuleFilterSet
|
||||
filterset_form = forms.ACLStandardRuleFilterForm
|
||||
|
||||
|
||||
class AccessListRuleEditView(generic.ObjectEditView):
|
||||
queryset = models.AccessListRule.objects.all()
|
||||
form = forms.AccessListRuleForm
|
||||
class ACLStandardRuleEditView(generic.ObjectEditView):
|
||||
"""
|
||||
Defines the edit view for the ACLStandardRule django model.
|
||||
"""
|
||||
queryset = models.ACLStandardRule.objects.all()
|
||||
form = forms.ACLStandardRuleForm
|
||||
|
||||
|
||||
class AccessListRuleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = models.AccessListRule.objects.all()
|
||||
class ACLStandardRuleDeleteView(generic.ObjectDeleteView):
|
||||
"""
|
||||
Defines the delete view for the ACLStandardRules django model.
|
||||
"""
|
||||
queryset = models.ACLStandardRule.objects.all()
|
||||
|
||||
#
|
||||
# ACLExtendedRule views
|
||||
#
|
||||
|
||||
|
||||
class ACLExtendedRuleView(generic.ObjectView):
|
||||
"""
|
||||
Defines the view for the ACLExtendedRule django model.
|
||||
"""
|
||||
queryset = models.ACLExtendedRule.objects.all()
|
||||
|
||||
|
||||
class ACLExtendedRuleListView(generic.ObjectListView):
|
||||
"""
|
||||
Defines the list view for the ACLExtendedRule django model.
|
||||
"""
|
||||
queryset = models.ACLExtendedRule.objects.all()
|
||||
table = tables.ACLExtendedRuleTable
|
||||
filterset = filtersets.ACLExtendedRuleFilterSet
|
||||
filterset_form = forms.ACLExtendedRuleFilterForm
|
||||
|
||||
|
||||
class ACLExtendedRuleEditView(generic.ObjectEditView):
|
||||
"""
|
||||
Defines the edit view for the ACLExtendedRule django model.
|
||||
"""
|
||||
queryset = models.ACLExtendedRule.objects.all()
|
||||
form = forms.ACLExtendedRuleForm
|
||||
|
||||
|
||||
class ACLExtendedRuleDeleteView(generic.ObjectDeleteView):
|
||||
"""
|
||||
Defines the delete view for the ACLExtendedRules django model.
|
||||
"""
|
||||
queryset = models.ACLExtendedRule.objects.all()
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
[MASTER]
|
||||
load-plugins=pylint_django
|
||||
django-settings-module=/opt/netbox/netbox/netbox/netbox.settings
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
|
||||
@ -25,5 +29,27 @@ disable = [
|
||||
"unsubscriptable-object",
|
||||
]
|
||||
enable = [
|
||||
"expression-not-assigned",
|
||||
"global-variable-not-assigned",
|
||||
"possibly-unused-variable",
|
||||
"reimported",
|
||||
"unused-argument",
|
||||
"unused-import",
|
||||
"unused-variable",
|
||||
"unused-wildcard-import",
|
||||
"useless-else-on-loop",
|
||||
"useless-import-alias",
|
||||
"useless-object-inheritance",
|
||||
"useless-parent-delegation",
|
||||
"useless-return",
|
||||
"useless-suppression", # Identify unneeded pylint disable statements
|
||||
]
|
||||
|
||||
[tool.isort]
|
||||
force_grid_wrap = 0
|
||||
include_trailing_comma = true
|
||||
line_length = 79
|
||||
multi_line_output = 1
|
||||
overwrite_in_place = true
|
||||
use_parentheses = true
|
||||
verbose = true
|
||||
4
setup.py
4
setup.py
@ -2,7 +2,7 @@
|
||||
#import os.path
|
||||
#
|
||||
from setuptools import find_packages, setup
|
||||
#
|
||||
|
||||
#
|
||||
#with open("README.md", "r") as fh:
|
||||
# long_description = fh.read()
|
||||
@ -27,7 +27,7 @@ setup(
|
||||
name='netbox-access-lists',
|
||||
version='0.1.0',
|
||||
#version=get_version('netbox_access_lists/version.py'),
|
||||
description='A NetBox plugin for Access-List management',
|
||||
description='A NetBox plugin for Access List management',
|
||||
#long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url='https://github.com/ryanmerolle/netbox-access-lists',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user