netbox-plugin-dns 0.21.4__py3-none-any.whl → 1.4.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_dns/__init__.py +106 -41
- netbox_dns/api/field_serializers.py +25 -0
- netbox_dns/api/nested_serializers.py +95 -52
- netbox_dns/api/serializers.py +14 -296
- netbox_dns/api/serializers_/__init__.py +0 -0
- netbox_dns/api/serializers_/dnssec_key_template.py +69 -0
- netbox_dns/api/serializers_/dnssec_policy.py +165 -0
- netbox_dns/api/serializers_/nameserver.py +56 -0
- netbox_dns/api/serializers_/prefix.py +18 -0
- netbox_dns/api/serializers_/record.py +105 -0
- netbox_dns/api/serializers_/record_template.py +71 -0
- netbox_dns/api/serializers_/registrar.py +45 -0
- netbox_dns/api/serializers_/registration_contact.py +50 -0
- netbox_dns/api/serializers_/view.py +81 -0
- netbox_dns/api/serializers_/zone.py +247 -0
- netbox_dns/api/serializers_/zone_template.py +157 -0
- netbox_dns/api/urls.py +13 -2
- netbox_dns/api/views.py +96 -58
- netbox_dns/choices/__init__.py +4 -0
- netbox_dns/choices/dnssec_key_template.py +67 -0
- netbox_dns/choices/dnssec_policy.py +40 -0
- netbox_dns/choices/record.py +104 -0
- netbox_dns/choices/utilities.py +4 -0
- netbox_dns/choices/zone.py +119 -0
- netbox_dns/fields/__init__.py +4 -0
- netbox_dns/fields/address.py +22 -16
- netbox_dns/fields/choice_array.py +33 -0
- netbox_dns/fields/ipam.py +15 -0
- netbox_dns/fields/network.py +42 -18
- netbox_dns/fields/rfc2317.py +97 -0
- netbox_dns/fields/timeperiod.py +33 -0
- netbox_dns/filters.py +7 -0
- netbox_dns/filtersets/__init__.py +12 -0
- netbox_dns/filtersets/dnssec_key_template.py +57 -0
- netbox_dns/filtersets/dnssec_policy.py +101 -0
- netbox_dns/filtersets/nameserver.py +46 -0
- netbox_dns/filtersets/record.py +135 -0
- netbox_dns/filtersets/record_template.py +59 -0
- netbox_dns/{filters → filtersets}/registrar.py +8 -1
- netbox_dns/{filters/contact.py → filtersets/registration_contact.py} +9 -3
- netbox_dns/filtersets/view.py +45 -0
- netbox_dns/filtersets/zone.py +254 -0
- netbox_dns/filtersets/zone_template.py +165 -0
- netbox_dns/forms/__init__.py +5 -1
- netbox_dns/forms/dnssec_key_template.py +250 -0
- netbox_dns/forms/dnssec_policy.py +654 -0
- netbox_dns/forms/nameserver.py +121 -27
- netbox_dns/forms/record.py +215 -104
- netbox_dns/forms/record_template.py +285 -0
- netbox_dns/forms/registrar.py +108 -31
- netbox_dns/forms/registration_contact.py +282 -0
- netbox_dns/forms/view.py +331 -20
- netbox_dns/forms/zone.py +769 -373
- netbox_dns/forms/zone_template.py +463 -0
- netbox_dns/graphql/__init__.py +25 -22
- netbox_dns/graphql/enums.py +41 -0
- netbox_dns/graphql/filter_lookups.py +13 -0
- netbox_dns/graphql/filters/__init__.py +12 -0
- netbox_dns/graphql/filters/dnssec_key_template.py +63 -0
- netbox_dns/graphql/filters/dnssec_policy.py +124 -0
- netbox_dns/graphql/filters/nameserver.py +32 -0
- netbox_dns/graphql/filters/record.py +89 -0
- netbox_dns/graphql/filters/record_template.py +55 -0
- netbox_dns/graphql/filters/registrar.py +30 -0
- netbox_dns/graphql/filters/registration_contact.py +27 -0
- netbox_dns/graphql/filters/view.py +28 -0
- netbox_dns/graphql/filters/zone.py +147 -0
- netbox_dns/graphql/filters/zone_template.py +97 -0
- netbox_dns/graphql/schema.py +89 -7
- netbox_dns/graphql/types.py +355 -0
- netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
- netbox_dns/locale/en/LC_MESSAGES/django.mo +0 -0
- netbox_dns/locale/fr/LC_MESSAGES/django.mo +0 -0
- netbox_dns/management/commands/cleanup_database.py +175 -156
- netbox_dns/management/commands/cleanup_rrset_ttl.py +64 -0
- netbox_dns/management/commands/rebuild_dnssync.py +23 -0
- netbox_dns/management/commands/setup_dnssync.py +140 -0
- netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py +0 -27
- netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +557 -0
- netbox_dns/migrations/{0013_add_nameserver_zone_record_description.py → 0002_contact_description_registrar_description.py} +4 -9
- netbox_dns/migrations/0003_default_view.py +15 -0
- netbox_dns/migrations/0004_create_and_assign_default_view.py +26 -0
- netbox_dns/migrations/0005_alter_zone_view_not_null.py +18 -0
- netbox_dns/migrations/0006_templating.py +172 -0
- netbox_dns/migrations/0007_alter_ordering_options.py +25 -0
- netbox_dns/migrations/0008_view_prefixes.py +18 -0
- netbox_dns/migrations/0009_rename_contact_registrationcontact.py +36 -0
- netbox_dns/migrations/0010_view_ip_address_filter.py +18 -0
- netbox_dns/migrations/0011_rename_related_fields.py +63 -0
- netbox_dns/migrations/0012_natural_ordering.py +88 -0
- netbox_dns/migrations/0013_zonetemplate_soa_mname_zonetemplate_soa_rname.py +30 -0
- netbox_dns/migrations/0014_alter_unique_constraints_lowercase.py +42 -0
- netbox_dns/migrations/0015_dnssec.py +168 -0
- netbox_dns/migrations/{0015_add_record_status.py → 0016_dnssec_policy_status.py} +5 -4
- netbox_dns/migrations/0017_dnssec_policy_zone_zone_template.py +41 -0
- netbox_dns/migrations/0018_zone_domain_status_zone_expiration_date.py +23 -0
- netbox_dns/migrations/0019_dnssecpolicy_parental_agents.py +25 -0
- netbox_dns/migrations/0020_netbox_3_4.py +1 -1
- netbox_dns/migrations/0020_remove_dnssecpolicy_parental_agents_and_more.py +29 -0
- netbox_dns/migrations/0021_alter_record_ptr_record.py +25 -0
- netbox_dns/migrations/0021_record_ip_address.py +1 -1
- netbox_dns/migrations/0022_alter_record_ipam_ip_address.py +26 -0
- netbox_dns/migrations/0023_disable_ptr_false.py +27 -0
- netbox_dns/migrations/0024_zonetemplate_parental_agents.py +25 -0
- netbox_dns/migrations/0025_remove_zone_inline_signing_and_more.py +22 -0
- netbox_dns/migrations/0026_alter_dnssecpolicy_nsec3_opt_out.py +18 -0
- netbox_dns/migrations/0026_domain_registration.py +1 -1
- netbox_dns/migrations/0027_zone_comments.py +18 -0
- netbox_dns/migrations/0028_alter_zone_default_ttl_alter_zone_soa_minimum_and_more.py +54 -0
- netbox_dns/migrations/0028_rfc2317_fields.py +44 -0
- netbox_dns/migrations/0029_alter_registrationcontact_street.py +18 -0
- netbox_dns/migrations/0029_record_fqdn.py +30 -0
- netbox_dns/mixins/__init__.py +1 -0
- netbox_dns/mixins/object_modification.py +57 -0
- netbox_dns/models/__init__.py +5 -1
- netbox_dns/models/dnssec_key_template.py +114 -0
- netbox_dns/models/dnssec_policy.py +203 -0
- netbox_dns/models/nameserver.py +61 -30
- netbox_dns/models/record.py +781 -234
- netbox_dns/models/record_template.py +198 -0
- netbox_dns/models/registrar.py +34 -15
- netbox_dns/models/{contact.py → registration_contact.py} +72 -43
- netbox_dns/models/view.py +129 -9
- netbox_dns/models/zone.py +806 -242
- netbox_dns/models/zone_template.py +209 -0
- netbox_dns/navigation.py +176 -76
- netbox_dns/signals/__init__.py +0 -0
- netbox_dns/signals/dnssec.py +32 -0
- netbox_dns/signals/ipam_dnssync.py +216 -0
- netbox_dns/tables/__init__.py +5 -1
- netbox_dns/tables/dnssec_key_template.py +49 -0
- netbox_dns/tables/dnssec_policy.py +140 -0
- netbox_dns/tables/ipam_dnssync.py +12 -0
- netbox_dns/tables/nameserver.py +14 -17
- netbox_dns/tables/record.py +117 -59
- netbox_dns/tables/record_template.py +91 -0
- netbox_dns/tables/registrar.py +20 -10
- netbox_dns/tables/{contact.py → registration_contact.py} +22 -11
- netbox_dns/tables/view.py +47 -3
- netbox_dns/tables/zone.py +62 -31
- netbox_dns/tables/zone_template.py +78 -0
- netbox_dns/template_content.py +124 -38
- netbox_dns/templates/netbox_dns/dnsseckeytemplate.html +70 -0
- netbox_dns/templates/netbox_dns/dnssecpolicy.html +163 -0
- netbox_dns/templates/netbox_dns/nameserver.html +31 -28
- netbox_dns/templates/netbox_dns/record/managed.html +2 -1
- netbox_dns/templates/netbox_dns/record/related.html +17 -6
- netbox_dns/templates/netbox_dns/record.html +140 -93
- netbox_dns/templates/netbox_dns/recordtemplate.html +96 -0
- netbox_dns/templates/netbox_dns/registrar.html +41 -34
- netbox_dns/templates/netbox_dns/registrationcontact.html +76 -0
- netbox_dns/templates/netbox_dns/view/button.html +10 -0
- netbox_dns/templates/netbox_dns/view/prefix.html +44 -0
- netbox_dns/templates/netbox_dns/view/related.html +33 -0
- netbox_dns/templates/netbox_dns/view.html +62 -18
- netbox_dns/templates/netbox_dns/zone/base.html +6 -3
- netbox_dns/templates/netbox_dns/zone/child.html +6 -5
- netbox_dns/templates/netbox_dns/zone/child_zone.html +18 -0
- netbox_dns/templates/netbox_dns/zone/delegation_record.html +18 -0
- netbox_dns/templates/netbox_dns/zone/managed_record.html +1 -1
- netbox_dns/templates/netbox_dns/zone/record.html +6 -5
- netbox_dns/templates/netbox_dns/zone/registration.html +43 -24
- netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html +18 -0
- netbox_dns/templates/netbox_dns/zone.html +178 -119
- netbox_dns/templates/netbox_dns/zonetemplate/child.html +46 -0
- netbox_dns/templates/netbox_dns/zonetemplate.html +124 -0
- netbox_dns/templatetags/netbox_dns.py +10 -0
- netbox_dns/urls.py +50 -210
- netbox_dns/utilities/__init__.py +3 -0
- netbox_dns/{utilities.py → utilities/conversions.py} +55 -7
- netbox_dns/utilities/dns.py +11 -0
- netbox_dns/utilities/ipam_dnssync.py +370 -0
- netbox_dns/validators/__init__.py +4 -0
- netbox_dns/validators/dns_name.py +116 -0
- netbox_dns/validators/dns_value.py +147 -0
- netbox_dns/validators/dnssec.py +148 -0
- netbox_dns/validators/rfc2317.py +28 -0
- netbox_dns/views/__init__.py +5 -1
- netbox_dns/views/dnssec_key_template.py +78 -0
- netbox_dns/views/dnssec_policy.py +146 -0
- netbox_dns/views/nameserver.py +34 -15
- netbox_dns/views/record.py +156 -15
- netbox_dns/views/record_template.py +93 -0
- netbox_dns/views/registrar.py +32 -13
- netbox_dns/views/registration_contact.py +101 -0
- netbox_dns/views/view.py +58 -14
- netbox_dns/views/zone.py +130 -33
- netbox_dns/views/zone_template.py +82 -0
- netbox_plugin_dns-1.4.7.dist-info/METADATA +132 -0
- netbox_plugin_dns-1.4.7.dist-info/RECORD +201 -0
- {netbox_plugin_dns-0.21.4.dist-info → netbox_plugin_dns-1.4.7.dist-info}/WHEEL +2 -1
- {netbox_plugin_dns-0.21.4.dist-info → netbox_plugin_dns-1.4.7.dist-info/licenses}/LICENSE +2 -1
- netbox_plugin_dns-1.4.7.dist-info/top_level.txt +1 -0
- netbox_dns/filters/__init__.py +0 -6
- netbox_dns/filters/nameserver.py +0 -18
- netbox_dns/filters/record.py +0 -53
- netbox_dns/filters/view.py +0 -18
- netbox_dns/filters/zone.py +0 -112
- netbox_dns/forms/contact.py +0 -211
- netbox_dns/graphql/contact.py +0 -19
- netbox_dns/graphql/nameserver.py +0 -19
- netbox_dns/graphql/record.py +0 -19
- netbox_dns/graphql/registrar.py +0 -19
- netbox_dns/graphql/view.py +0 -19
- netbox_dns/graphql/zone.py +0 -19
- netbox_dns/management/commands/setup_coupling.py +0 -75
- netbox_dns/management/commands/update_soa.py +0 -22
- netbox_dns/middleware.py +0 -226
- netbox_dns/migrations/0001_initial.py +0 -115
- netbox_dns/migrations/0002_zone_default_ttl.py +0 -18
- netbox_dns/migrations/0003_soa_managed_records.py +0 -112
- netbox_dns/migrations/0004_create_ptr_for_a_aaaa_records.py +0 -80
- netbox_dns/migrations/0005_update_ns_records.py +0 -41
- netbox_dns/migrations/0006_zone_soa_serial_auto.py +0 -29
- netbox_dns/migrations/0007_alter_zone_soa_serial_auto.py +0 -17
- netbox_dns/migrations/0008_zone_status_names.py +0 -21
- netbox_dns/migrations/0009_netbox32.py +0 -71
- netbox_dns/migrations/0010_update_soa_records.py +0 -58
- netbox_dns/migrations/0011_add_view_model.py +0 -70
- netbox_dns/migrations/0012_adjust_zone_and_record.py +0 -17
- netbox_dns/migrations/0014_add_view_description.py +0 -16
- netbox_dns/migrations/0016_cleanup_ptr_records.py +0 -38
- netbox_dns/migrations/0017_alter_record_ttl.py +0 -17
- netbox_dns/migrations/0018_zone_arpa_network.py +0 -51
- netbox_dns/migrations/0019_update_ns_ttl.py +0 -19
- netbox_dns/templates/netbox_dns/contact.html +0 -71
- netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -21
- netbox_dns/templatetags/view_helpers.py +0 -15
- netbox_dns/validators.py +0 -57
- netbox_dns/views/contact.py +0 -83
- netbox_plugin_dns-0.21.4.dist-info/METADATA +0 -101
- netbox_plugin_dns-0.21.4.dist-info/RECORD +0 -110
netbox_dns/models/zone.py
CHANGED
|
@@ -1,54 +1,74 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from math import ceil
|
|
2
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, date
|
|
3
4
|
|
|
4
5
|
from dns import name as dns_name
|
|
5
|
-
from dns.rdtypes.ANY import SOA
|
|
6
6
|
from dns.exception import DNSException
|
|
7
|
-
|
|
7
|
+
from dns.rdtypes.ANY import SOA
|
|
8
8
|
from django.core.validators import (
|
|
9
9
|
MinValueValidator,
|
|
10
10
|
MaxValueValidator,
|
|
11
11
|
)
|
|
12
12
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
13
13
|
from django.db import models, transaction
|
|
14
|
-
from django.db.models import Q, Max, ExpressionWrapper, BooleanField
|
|
15
|
-
from django.
|
|
14
|
+
from django.db.models import Q, Max, ExpressionWrapper, BooleanField, UniqueConstraint
|
|
15
|
+
from django.db.models.functions import Length, Lower
|
|
16
16
|
from django.db.models.signals import m2m_changed
|
|
17
17
|
from django.dispatch import receiver
|
|
18
|
+
from django.conf import settings
|
|
19
|
+
from django.contrib.postgres.fields import ArrayField
|
|
20
|
+
from django.utils.translation import gettext_lazy as _
|
|
18
21
|
|
|
19
22
|
from netbox.models import NetBoxModel
|
|
23
|
+
from netbox.models.features import ContactsMixin
|
|
20
24
|
from netbox.search import SearchIndex, register_search
|
|
25
|
+
from netbox.plugins.utils import get_plugin_config
|
|
21
26
|
from utilities.querysets import RestrictedQuerySet
|
|
22
|
-
from utilities.choices import ChoiceSet
|
|
23
27
|
from ipam.models import IPAddress
|
|
28
|
+
from ipam.choices import IPAddressFamilyChoices
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from netbox_dns.fields import NetworkField
|
|
30
|
+
from netbox_dns.choices import (
|
|
31
|
+
RecordClassChoices,
|
|
32
|
+
RecordTypeChoices,
|
|
33
|
+
ZoneStatusChoices,
|
|
34
|
+
ZoneEPPStatusChoices,
|
|
35
|
+
)
|
|
36
|
+
from netbox_dns.fields import NetworkField, RFC2317NetworkField
|
|
33
37
|
from netbox_dns.utilities import (
|
|
38
|
+
update_dns_records,
|
|
39
|
+
check_dns_records,
|
|
40
|
+
get_ip_addresses_by_zone,
|
|
34
41
|
arpa_to_prefix,
|
|
35
42
|
name_to_unicode,
|
|
36
43
|
normalize_name,
|
|
44
|
+
get_parent_zone_names,
|
|
45
|
+
regex_from_list,
|
|
37
46
|
NameFormatError,
|
|
38
47
|
)
|
|
39
48
|
from netbox_dns.validators import (
|
|
40
|
-
|
|
49
|
+
validate_rname,
|
|
41
50
|
validate_domain_name,
|
|
42
51
|
)
|
|
52
|
+
from netbox_dns.mixins import ObjectModificationMixin
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
from .record import Record
|
|
55
|
+
from .view import View
|
|
56
|
+
from .nameserver import NameServer
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = (
|
|
60
|
+
"Zone",
|
|
61
|
+
"ZoneIndex",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
ZONE_ACTIVE_STATUS_LIST = get_plugin_config("netbox_dns", "zone_active_status")
|
|
65
|
+
RECORD_ACTIVE_STATUS_LIST = get_plugin_config("netbox_dns", "record_active_status")
|
|
48
66
|
|
|
49
67
|
|
|
50
68
|
class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
51
|
-
"""
|
|
69
|
+
"""
|
|
70
|
+
Custom manager for zones providing the activity status annotation
|
|
71
|
+
"""
|
|
52
72
|
|
|
53
73
|
def get_queryset(self):
|
|
54
74
|
return (
|
|
@@ -56,220 +76,336 @@ class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
56
76
|
.get_queryset()
|
|
57
77
|
.annotate(
|
|
58
78
|
active=ExpressionWrapper(
|
|
59
|
-
Q(status__in=
|
|
79
|
+
Q(status__in=ZONE_ACTIVE_STATUS_LIST), output_field=BooleanField()
|
|
60
80
|
)
|
|
61
81
|
)
|
|
62
82
|
)
|
|
63
83
|
|
|
64
84
|
|
|
65
|
-
class
|
|
66
|
-
|
|
85
|
+
class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
86
|
+
class Meta:
|
|
87
|
+
verbose_name = _("Zone")
|
|
88
|
+
verbose_name_plural = _("Zones")
|
|
67
89
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
ordering = (
|
|
91
|
+
"view",
|
|
92
|
+
"name",
|
|
93
|
+
)
|
|
72
94
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
constraints = [
|
|
96
|
+
UniqueConstraint(
|
|
97
|
+
Lower("name"),
|
|
98
|
+
"view",
|
|
99
|
+
name="name_view_unique_ci",
|
|
100
|
+
violation_error_message=_(
|
|
101
|
+
"There is already a zone with the same name in this view"
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
clone_fields = (
|
|
107
|
+
"view",
|
|
108
|
+
"name",
|
|
109
|
+
"description",
|
|
110
|
+
"status",
|
|
111
|
+
"nameservers",
|
|
112
|
+
"default_ttl",
|
|
113
|
+
"soa_ttl",
|
|
114
|
+
"soa_mname",
|
|
115
|
+
"soa_rname",
|
|
116
|
+
"soa_refresh",
|
|
117
|
+
"soa_retry",
|
|
118
|
+
"soa_expire",
|
|
119
|
+
"soa_minimum",
|
|
120
|
+
"tenant",
|
|
121
|
+
)
|
|
79
122
|
|
|
123
|
+
soa_clean_fields = {
|
|
124
|
+
"description",
|
|
125
|
+
"status",
|
|
126
|
+
"dnssec_policy",
|
|
127
|
+
"parental_agents",
|
|
128
|
+
"registrar",
|
|
129
|
+
"registry_domain_id",
|
|
130
|
+
"expiration_date",
|
|
131
|
+
"domain_status",
|
|
132
|
+
"registrant",
|
|
133
|
+
"admin_c",
|
|
134
|
+
"tech_c",
|
|
135
|
+
"billing_c",
|
|
136
|
+
"rfc2317_parent_managed",
|
|
137
|
+
"tenant",
|
|
138
|
+
"comments",
|
|
139
|
+
}
|
|
80
140
|
|
|
81
|
-
|
|
82
|
-
|
|
141
|
+
objects = ZoneManager()
|
|
142
|
+
|
|
143
|
+
def __str__(self):
|
|
144
|
+
if self.name == "." and get_plugin_config("netbox_dns", "enable_root_zones"):
|
|
145
|
+
name = ". (root zone)"
|
|
146
|
+
else:
|
|
147
|
+
try:
|
|
148
|
+
name = dns_name.from_text(self.name, origin=None).to_unicode()
|
|
149
|
+
except DNSException:
|
|
150
|
+
name = self.name
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
if not self.view.default_view:
|
|
154
|
+
return f"[{self.view}] {name}"
|
|
155
|
+
except ObjectDoesNotExist:
|
|
156
|
+
return f"[<no view assigned>] {name}"
|
|
157
|
+
|
|
158
|
+
return str(name)
|
|
159
|
+
|
|
160
|
+
def __init__(self, *args, **kwargs):
|
|
161
|
+
kwargs.pop("template", None)
|
|
162
|
+
|
|
163
|
+
super().__init__(*args, **kwargs)
|
|
164
|
+
|
|
165
|
+
self._soa_serial_dirty = False
|
|
166
|
+
self._ip_addresses_checked = False
|
|
83
167
|
|
|
84
168
|
view = models.ForeignKey(
|
|
169
|
+
verbose_name=_("View"),
|
|
85
170
|
to="View",
|
|
86
171
|
on_delete=models.PROTECT,
|
|
87
|
-
|
|
88
|
-
null=
|
|
172
|
+
related_name="zones",
|
|
173
|
+
null=False,
|
|
89
174
|
)
|
|
90
175
|
name = models.CharField(
|
|
176
|
+
verbose_name=_("Name"),
|
|
91
177
|
max_length=255,
|
|
178
|
+
db_collation="natural_sort",
|
|
179
|
+
)
|
|
180
|
+
description = models.CharField(
|
|
181
|
+
verbose_name=_("Description"),
|
|
182
|
+
max_length=200,
|
|
183
|
+
blank=True,
|
|
92
184
|
)
|
|
93
185
|
status = models.CharField(
|
|
186
|
+
verbose_name=_("Status"),
|
|
94
187
|
max_length=50,
|
|
95
188
|
choices=ZoneStatusChoices,
|
|
96
189
|
default=ZoneStatusChoices.STATUS_ACTIVE,
|
|
97
190
|
blank=True,
|
|
98
191
|
)
|
|
99
192
|
nameservers = models.ManyToManyField(
|
|
193
|
+
verbose_name=_("Nameserver"),
|
|
100
194
|
to="NameServer",
|
|
101
195
|
related_name="zones",
|
|
102
196
|
blank=True,
|
|
103
197
|
)
|
|
104
198
|
default_ttl = models.PositiveIntegerField(
|
|
199
|
+
verbose_name=_("Default TTL"),
|
|
105
200
|
blank=True,
|
|
106
|
-
|
|
107
|
-
validators=[MinValueValidator(1)],
|
|
201
|
+
validators=[MaxValueValidator(2147483647)],
|
|
108
202
|
)
|
|
109
203
|
soa_ttl = models.PositiveIntegerField(
|
|
204
|
+
verbose_name=_("SOA TTL"),
|
|
110
205
|
blank=False,
|
|
111
206
|
null=False,
|
|
112
|
-
|
|
113
|
-
validators=[MinValueValidator(1)],
|
|
207
|
+
validators=[MaxValueValidator(2147483647)],
|
|
114
208
|
)
|
|
115
209
|
soa_mname = models.ForeignKey(
|
|
210
|
+
verbose_name=_("SOA MName"),
|
|
116
211
|
to="NameServer",
|
|
117
|
-
related_name="
|
|
118
|
-
verbose_name="SOA MName",
|
|
212
|
+
related_name="soa_zones",
|
|
119
213
|
on_delete=models.PROTECT,
|
|
120
214
|
blank=False,
|
|
121
215
|
null=False,
|
|
122
216
|
)
|
|
123
217
|
soa_rname = models.CharField(
|
|
218
|
+
verbose_name=_("SOA RName"),
|
|
124
219
|
max_length=255,
|
|
125
220
|
blank=False,
|
|
126
221
|
null=False,
|
|
127
|
-
verbose_name="SOA RName",
|
|
128
222
|
)
|
|
129
223
|
soa_serial = models.BigIntegerField(
|
|
224
|
+
verbose_name=_("SOA Serial"),
|
|
130
225
|
blank=True,
|
|
131
226
|
null=True,
|
|
132
|
-
verbose_name="SOA Serial",
|
|
133
227
|
validators=[MinValueValidator(1), MaxValueValidator(4294967295)],
|
|
134
228
|
)
|
|
135
229
|
soa_refresh = models.PositiveIntegerField(
|
|
230
|
+
verbose_name=_("SOA Refresh"),
|
|
136
231
|
blank=False,
|
|
137
232
|
null=False,
|
|
138
|
-
verbose_name="SOA Refresh",
|
|
139
233
|
validators=[MinValueValidator(1)],
|
|
140
234
|
)
|
|
141
235
|
soa_retry = models.PositiveIntegerField(
|
|
236
|
+
verbose_name=_("SOA Retry"),
|
|
142
237
|
blank=False,
|
|
143
238
|
null=False,
|
|
144
|
-
verbose_name="SOA Retry",
|
|
145
239
|
validators=[MinValueValidator(1)],
|
|
146
240
|
)
|
|
147
241
|
soa_expire = models.PositiveIntegerField(
|
|
242
|
+
verbose_name=_("SOA Expire"),
|
|
148
243
|
blank=False,
|
|
149
244
|
null=False,
|
|
150
|
-
verbose_name="SOA Expire",
|
|
151
245
|
validators=[MinValueValidator(1)],
|
|
152
246
|
)
|
|
153
247
|
soa_minimum = models.PositiveIntegerField(
|
|
248
|
+
verbose_name=_("SOA Minimum TTL"),
|
|
154
249
|
blank=False,
|
|
155
250
|
null=False,
|
|
156
|
-
|
|
157
|
-
validators=[MinValueValidator(1)],
|
|
251
|
+
validators=[MaxValueValidator(2147483647)],
|
|
158
252
|
)
|
|
159
253
|
soa_serial_auto = models.BooleanField(
|
|
160
|
-
verbose_name="Generate SOA Serial",
|
|
161
|
-
help_text="Automatically generate the SOA
|
|
254
|
+
verbose_name=_("Generate SOA Serial"),
|
|
255
|
+
help_text=_("Automatically generate the SOA serial number"),
|
|
162
256
|
default=True,
|
|
163
257
|
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
verbose_name="ARPA Network",
|
|
170
|
-
help_text="Network related to a reverse lookup zone (.arpa)",
|
|
258
|
+
dnssec_policy = models.ForeignKey(
|
|
259
|
+
verbose_name=_("DNSSEC Policy"),
|
|
260
|
+
to="DNSSECPolicy",
|
|
261
|
+
on_delete=models.PROTECT,
|
|
262
|
+
related_name="zones",
|
|
171
263
|
blank=True,
|
|
172
264
|
null=True,
|
|
173
265
|
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
266
|
+
parental_agents = ArrayField(
|
|
267
|
+
base_field=models.GenericIPAddressField(
|
|
268
|
+
protocol="both",
|
|
269
|
+
),
|
|
178
270
|
blank=True,
|
|
179
271
|
null=True,
|
|
272
|
+
default=list,
|
|
180
273
|
)
|
|
181
274
|
registrar = models.ForeignKey(
|
|
275
|
+
verbose_name=_("Registrar"),
|
|
182
276
|
to="Registrar",
|
|
183
277
|
on_delete=models.SET_NULL,
|
|
184
|
-
|
|
185
|
-
help_text="The external registrar the domain is registered with",
|
|
278
|
+
related_name="zones",
|
|
186
279
|
blank=True,
|
|
187
280
|
null=True,
|
|
188
281
|
)
|
|
189
282
|
registry_domain_id = models.CharField(
|
|
190
|
-
verbose_name="Registry Domain ID",
|
|
191
|
-
help_text="The ID of the domain assigned by the registry",
|
|
283
|
+
verbose_name=_("Registry Domain ID"),
|
|
192
284
|
max_length=50,
|
|
193
285
|
blank=True,
|
|
194
286
|
null=True,
|
|
195
287
|
)
|
|
288
|
+
expiration_date = models.DateField(
|
|
289
|
+
verbose_name=_("Expiration Date"),
|
|
290
|
+
blank=True,
|
|
291
|
+
null=True,
|
|
292
|
+
)
|
|
293
|
+
domain_status = models.CharField(
|
|
294
|
+
verbose_name=_("Domain Status"),
|
|
295
|
+
max_length=50,
|
|
296
|
+
choices=ZoneEPPStatusChoices,
|
|
297
|
+
blank=True,
|
|
298
|
+
null=True,
|
|
299
|
+
)
|
|
196
300
|
registrant = models.ForeignKey(
|
|
197
|
-
|
|
301
|
+
verbose_name=_("Registrant"),
|
|
302
|
+
to="RegistrationContact",
|
|
198
303
|
on_delete=models.SET_NULL,
|
|
199
|
-
|
|
200
|
-
help_text="The owner of the domain",
|
|
304
|
+
related_name="registrant_zones",
|
|
201
305
|
blank=True,
|
|
202
306
|
null=True,
|
|
203
307
|
)
|
|
204
308
|
admin_c = models.ForeignKey(
|
|
205
|
-
|
|
309
|
+
verbose_name=_("Administrative Contact"),
|
|
310
|
+
to="RegistrationContact",
|
|
206
311
|
on_delete=models.SET_NULL,
|
|
207
|
-
verbose_name="Admin Contact",
|
|
208
312
|
related_name="admin_c_zones",
|
|
209
|
-
help_text="The administrative contact for the domain",
|
|
210
313
|
blank=True,
|
|
211
314
|
null=True,
|
|
212
315
|
)
|
|
213
316
|
tech_c = models.ForeignKey(
|
|
214
|
-
|
|
317
|
+
verbose_name=_("Technical Contact"),
|
|
318
|
+
to="RegistrationContact",
|
|
215
319
|
on_delete=models.SET_NULL,
|
|
216
|
-
verbose_name="Tech Contact",
|
|
217
320
|
related_name="tech_c_zones",
|
|
218
|
-
help_text="The technical contact for the domain",
|
|
219
321
|
blank=True,
|
|
220
322
|
null=True,
|
|
221
323
|
)
|
|
222
324
|
billing_c = models.ForeignKey(
|
|
223
|
-
|
|
325
|
+
verbose_name=_("Billing Contact"),
|
|
326
|
+
to="RegistrationContact",
|
|
224
327
|
on_delete=models.SET_NULL,
|
|
225
|
-
verbose_name="Billing Contact",
|
|
226
328
|
related_name="billing_c_zones",
|
|
227
|
-
help_text="The billing contact for the domain",
|
|
228
329
|
blank=True,
|
|
229
330
|
null=True,
|
|
230
331
|
)
|
|
332
|
+
rfc2317_prefix = RFC2317NetworkField(
|
|
333
|
+
verbose_name=_("RFC2317 Prefix"),
|
|
334
|
+
help_text=_("RFC2317 IPv4 prefix with a length of at least 25 bits"),
|
|
335
|
+
blank=True,
|
|
336
|
+
null=True,
|
|
337
|
+
)
|
|
338
|
+
rfc2317_parent_managed = models.BooleanField(
|
|
339
|
+
verbose_name=_("RFC2317 Parent Managed"),
|
|
340
|
+
help_text=_("The parent zone for the RFC2317 zone is managed by NetBox DNS"),
|
|
341
|
+
default=False,
|
|
342
|
+
)
|
|
343
|
+
rfc2317_parent_zone = models.ForeignKey(
|
|
344
|
+
verbose_name=_("RFC2317 Parent Zone"),
|
|
345
|
+
to="self",
|
|
346
|
+
on_delete=models.SET_NULL,
|
|
347
|
+
related_name="rfc2317_child_zones",
|
|
348
|
+
help_text=_("Parent zone for RFC2317 reverse zones"),
|
|
349
|
+
blank=True,
|
|
350
|
+
null=True,
|
|
351
|
+
)
|
|
352
|
+
arpa_network = NetworkField(
|
|
353
|
+
verbose_name=_("ARPA Network"),
|
|
354
|
+
help_text=_("Network related to a reverse lookup zone (.arpa)"),
|
|
355
|
+
blank=True,
|
|
356
|
+
null=True,
|
|
357
|
+
)
|
|
358
|
+
tenant = models.ForeignKey(
|
|
359
|
+
verbose_name=_("Tenant"),
|
|
360
|
+
to="tenancy.Tenant",
|
|
361
|
+
on_delete=models.PROTECT,
|
|
362
|
+
related_name="netbox_dns_zones",
|
|
363
|
+
blank=True,
|
|
364
|
+
null=True,
|
|
365
|
+
)
|
|
366
|
+
comments = models.TextField(
|
|
367
|
+
verbose_name=_("Comments"),
|
|
368
|
+
blank=True,
|
|
369
|
+
)
|
|
231
370
|
|
|
232
|
-
|
|
371
|
+
@property
|
|
372
|
+
def fqdn(self):
|
|
373
|
+
return f"{self.name}."
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def get_defaults():
|
|
377
|
+
default_fields = (
|
|
378
|
+
"zone_default_ttl",
|
|
379
|
+
"zone_soa_ttl",
|
|
380
|
+
"zone_soa_serial",
|
|
381
|
+
"zone_soa_refresh",
|
|
382
|
+
"zone_soa_retry",
|
|
383
|
+
"zone_soa_expire",
|
|
384
|
+
"zone_soa_minimum",
|
|
385
|
+
"zone_soa_rname",
|
|
386
|
+
)
|
|
233
387
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
"default_ttl",
|
|
240
|
-
"soa_ttl",
|
|
241
|
-
"soa_mname",
|
|
242
|
-
"soa_rname",
|
|
243
|
-
"soa_refresh",
|
|
244
|
-
"soa_retry",
|
|
245
|
-
"soa_expire",
|
|
246
|
-
"soa_minimum",
|
|
247
|
-
"description",
|
|
248
|
-
]
|
|
388
|
+
return {
|
|
389
|
+
field[5:]: value
|
|
390
|
+
for field, value in settings.PLUGINS_CONFIG.get("netbox_dns").items()
|
|
391
|
+
if field in default_fields
|
|
392
|
+
}
|
|
249
393
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
"name",
|
|
254
|
-
)
|
|
255
|
-
unique_together = (
|
|
256
|
-
"view",
|
|
257
|
-
"name",
|
|
258
|
-
)
|
|
394
|
+
@property
|
|
395
|
+
def soa_serial_dirty(self):
|
|
396
|
+
return self._soa_serial_dirty
|
|
259
397
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
else:
|
|
264
|
-
try:
|
|
265
|
-
name = dns_name.from_text(self.name, origin=None).to_unicode()
|
|
266
|
-
except dns_name.IDNAException:
|
|
267
|
-
name = self.name
|
|
398
|
+
@soa_serial_dirty.setter
|
|
399
|
+
def soa_serial_dirty(self, soa_serial_dirty):
|
|
400
|
+
self._soa_serial_dirty = soa_serial_dirty
|
|
268
401
|
|
|
269
|
-
|
|
270
|
-
|
|
402
|
+
@property
|
|
403
|
+
def ip_addresses_checked(self):
|
|
404
|
+
return self._ip_addresses_checked
|
|
271
405
|
|
|
272
|
-
|
|
406
|
+
@ip_addresses_checked.setter
|
|
407
|
+
def ip_addresses_checked(self, ip_addresses_checked):
|
|
408
|
+
self._ip_addresses_checked = ip_addresses_checked
|
|
273
409
|
|
|
274
410
|
@property
|
|
275
411
|
def display_name(self):
|
|
@@ -278,20 +414,41 @@ class Zone(NetBoxModel):
|
|
|
278
414
|
def get_status_color(self):
|
|
279
415
|
return ZoneStatusChoices.colors.get(self.status)
|
|
280
416
|
|
|
281
|
-
def
|
|
282
|
-
return
|
|
417
|
+
def get_domain_status_color(self):
|
|
418
|
+
return ZoneEPPStatusChoices.colors.get(self.domain_status)
|
|
283
419
|
|
|
284
420
|
def get_status_class(self):
|
|
285
421
|
return self.CSS_CLASSES.get(self.status)
|
|
286
422
|
|
|
287
423
|
@property
|
|
288
424
|
def is_active(self):
|
|
289
|
-
return self.status in
|
|
425
|
+
return self.status in ZONE_ACTIVE_STATUS_LIST
|
|
290
426
|
|
|
291
427
|
@property
|
|
292
428
|
def is_reverse_zone(self):
|
|
293
429
|
return self.name.endswith(".arpa") and self.network_from_name is not None
|
|
294
430
|
|
|
431
|
+
@property
|
|
432
|
+
def is_rfc2317_zone(self):
|
|
433
|
+
return self.rfc2317_prefix is not None
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def inline_signing(self):
|
|
437
|
+
if self.dnssec_policy is None:
|
|
438
|
+
return None
|
|
439
|
+
|
|
440
|
+
return self.dnssec_policy.inline_signing
|
|
441
|
+
|
|
442
|
+
def get_rfc2317_parent_zone(self):
|
|
443
|
+
if not self.is_rfc2317_zone:
|
|
444
|
+
return None
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
self.view.zones.filter(arpa_network__net_contains=self.rfc2317_prefix)
|
|
448
|
+
.order_by("arpa_network__net_mask_length")
|
|
449
|
+
.last()
|
|
450
|
+
)
|
|
451
|
+
|
|
295
452
|
@property
|
|
296
453
|
def is_registered(self):
|
|
297
454
|
return any(
|
|
@@ -303,24 +460,91 @@ class Zone(NetBoxModel):
|
|
|
303
460
|
self.admin_c,
|
|
304
461
|
self.tech_c,
|
|
305
462
|
self.billing_c,
|
|
463
|
+
self.expiration_date,
|
|
464
|
+
self.domain_status,
|
|
306
465
|
)
|
|
307
466
|
)
|
|
308
467
|
|
|
309
468
|
@property
|
|
310
|
-
def
|
|
311
|
-
|
|
312
|
-
return Q(view__isnull=True)
|
|
313
|
-
return Q(view=self.view)
|
|
469
|
+
def child_zones(self):
|
|
470
|
+
return self.view.zones.filter(name__iregex=rf"^[^.]+\.{re.escape(self.name)}$")
|
|
314
471
|
|
|
315
|
-
|
|
316
|
-
|
|
472
|
+
@property
|
|
473
|
+
def descendant_zones(self):
|
|
474
|
+
return self.view.zones.filter(name__iendswith=f".{self.name}")
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def parent_zone(self):
|
|
478
|
+
try:
|
|
479
|
+
return self.view.zones.get(
|
|
480
|
+
name__iexact=get_parent_zone_names(self.name)[-1]
|
|
481
|
+
)
|
|
482
|
+
except (Zone.DoesNotExist, IndexError):
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
@property
|
|
486
|
+
def ancestor_zones(self):
|
|
487
|
+
return (
|
|
488
|
+
self.view.zones.annotate(name_length=Length("name"))
|
|
489
|
+
.filter(name__iregex=regex_from_list(get_parent_zone_names(self.name)))
|
|
490
|
+
.order_by("name_length")
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def delegation_records(self):
|
|
495
|
+
descendant_zone_names = [
|
|
496
|
+
rf"{name}."
|
|
497
|
+
for name in (
|
|
498
|
+
name.lower()
|
|
499
|
+
for name in self.descendant_zones.values_list("name", flat=True)
|
|
500
|
+
)
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
ns_records = (
|
|
504
|
+
self.records.filter(type=RecordTypeChoices.NS)
|
|
505
|
+
.exclude(fqdn__iexact=self.fqdn)
|
|
506
|
+
.filter(fqdn__iregex=regex_from_list(descendant_zone_names))
|
|
507
|
+
)
|
|
508
|
+
ns_values = [record.value_fqdn for record in ns_records]
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
ns_records
|
|
512
|
+
| self.records.filter(type=RecordTypeChoices.DS)
|
|
513
|
+
| self.records.filter(
|
|
514
|
+
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
|
|
515
|
+
fqdn__in=ns_values,
|
|
516
|
+
)
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
@property
|
|
520
|
+
def ancestor_delegation_records(self):
|
|
521
|
+
ancestor_zones = self.ancestor_zones
|
|
522
|
+
|
|
523
|
+
ns_records = Record.objects.filter(
|
|
524
|
+
type=RecordTypeChoices.NS, zone__in=ancestor_zones, fqdn=self.fqdn
|
|
525
|
+
)
|
|
526
|
+
ns_values = [record.value_fqdn for record in ns_records]
|
|
527
|
+
|
|
528
|
+
ds_records = Record.objects.filter(
|
|
529
|
+
type=RecordTypeChoices.DS, zone__in=ancestor_zones, fqdn=self.fqdn
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
return (
|
|
533
|
+
ns_records
|
|
534
|
+
| ds_records
|
|
535
|
+
| Record.objects.filter(
|
|
536
|
+
zone__in=ancestor_zones,
|
|
537
|
+
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
|
|
538
|
+
fqdn__in=ns_values,
|
|
539
|
+
)
|
|
540
|
+
)
|
|
317
541
|
|
|
318
542
|
def update_soa_record(self):
|
|
319
543
|
soa_name = "@"
|
|
320
544
|
soa_ttl = self.soa_ttl
|
|
321
545
|
soa_rdata = SOA.SOA(
|
|
322
|
-
rdclass=
|
|
323
|
-
rdtype=
|
|
546
|
+
rdclass=RecordClassChoices.IN,
|
|
547
|
+
rdtype=RecordTypeChoices.SOA,
|
|
324
548
|
mname=self.soa_mname.name,
|
|
325
549
|
rname=self.soa_rname,
|
|
326
550
|
serial=self.soa_serial,
|
|
@@ -331,20 +555,23 @@ class Zone(NetBoxModel):
|
|
|
331
555
|
)
|
|
332
556
|
|
|
333
557
|
try:
|
|
334
|
-
soa_record = self.
|
|
335
|
-
type=record.RecordTypeChoices.SOA, name=soa_name
|
|
336
|
-
)
|
|
558
|
+
soa_record = self.records.get(type=RecordTypeChoices.SOA, name=soa_name)
|
|
337
559
|
|
|
338
|
-
if
|
|
560
|
+
if (
|
|
561
|
+
soa_record.ttl != soa_ttl
|
|
562
|
+
or soa_record.value != soa_rdata.to_text()
|
|
563
|
+
or not soa_record.managed
|
|
564
|
+
):
|
|
565
|
+
soa_record.snapshot()
|
|
339
566
|
soa_record.ttl = soa_ttl
|
|
340
567
|
soa_record.value = soa_rdata.to_text()
|
|
341
568
|
soa_record.managed = True
|
|
342
569
|
soa_record.save()
|
|
343
570
|
|
|
344
|
-
except
|
|
345
|
-
|
|
571
|
+
except Record.DoesNotExist:
|
|
572
|
+
Record.objects.create(
|
|
346
573
|
zone_id=self.pk,
|
|
347
|
-
type=
|
|
574
|
+
type=RecordTypeChoices.SOA,
|
|
348
575
|
name=soa_name,
|
|
349
576
|
ttl=soa_ttl,
|
|
350
577
|
value=soa_rdata.to_text(),
|
|
@@ -356,19 +583,44 @@ class Zone(NetBoxModel):
|
|
|
356
583
|
|
|
357
584
|
nameservers = [f"{nameserver.name}." for nameserver in self.nameservers.all()]
|
|
358
585
|
|
|
359
|
-
self.
|
|
360
|
-
|
|
361
|
-
).
|
|
586
|
+
for ns_record in self.records.filter(
|
|
587
|
+
type=RecordTypeChoices.NS, managed=True
|
|
588
|
+
).exclude(value__in=nameservers):
|
|
589
|
+
ns_record.delete()
|
|
362
590
|
|
|
363
591
|
for ns in nameservers:
|
|
364
|
-
|
|
592
|
+
Record.raw_objects.update_or_create(
|
|
365
593
|
zone_id=self.pk,
|
|
366
|
-
type=
|
|
594
|
+
type=RecordTypeChoices.NS,
|
|
367
595
|
name=ns_name,
|
|
368
596
|
value=ns,
|
|
369
597
|
managed=True,
|
|
370
598
|
)
|
|
371
599
|
|
|
600
|
+
def _check_nameserver_address_records(self, nameserver):
|
|
601
|
+
name = dns_name.from_text(nameserver.name, origin=None)
|
|
602
|
+
parent = name.parent()
|
|
603
|
+
|
|
604
|
+
if len(parent) < 2:
|
|
605
|
+
return None
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
ns_zone = Zone.objects.get(view_id=self.view.pk, name=parent.to_text())
|
|
609
|
+
except ObjectDoesNotExist:
|
|
610
|
+
return None
|
|
611
|
+
|
|
612
|
+
relative_name = name.relativize(parent).to_text()
|
|
613
|
+
address_records = ns_zone.records.filter(
|
|
614
|
+
Q(status__in=RECORD_ACTIVE_STATUS_LIST),
|
|
615
|
+
Q(Q(name=f"{nameserver.name}.") | Q(name=relative_name)),
|
|
616
|
+
Q(Q(type=RecordTypeChoices.A) | Q(type=RecordTypeChoices.AAAA)),
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
if not address_records.exists():
|
|
620
|
+
return _(
|
|
621
|
+
"Nameserver {ns} does not have an active address record in zone {zone}"
|
|
622
|
+
).format(ns=nameserver.name, zone=ns_zone)
|
|
623
|
+
|
|
372
624
|
def check_nameservers(self):
|
|
373
625
|
nameservers = self.nameservers.all()
|
|
374
626
|
|
|
@@ -376,45 +628,59 @@ class Zone(NetBoxModel):
|
|
|
376
628
|
ns_errors = []
|
|
377
629
|
|
|
378
630
|
if not nameservers:
|
|
379
|
-
ns_errors.append(
|
|
631
|
+
ns_errors.append(
|
|
632
|
+
_("No nameservers are configured for zone {zone}").format(zone=self)
|
|
633
|
+
)
|
|
380
634
|
|
|
381
|
-
for
|
|
382
|
-
|
|
383
|
-
|
|
635
|
+
for _nameserver in nameservers:
|
|
636
|
+
warning = self._check_nameserver_address_records(_nameserver)
|
|
637
|
+
if warning is not None:
|
|
638
|
+
ns_warnings.append(warning)
|
|
384
639
|
|
|
385
|
-
|
|
386
|
-
continue
|
|
640
|
+
return ns_warnings, ns_errors
|
|
387
641
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
)
|
|
642
|
+
def check_soa_mname(self):
|
|
643
|
+
return self._check_nameserver_address_records(self.soa_mname)
|
|
391
644
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
645
|
+
def check_expiration(self):
|
|
646
|
+
if self.expiration_date is None:
|
|
647
|
+
return None, None
|
|
648
|
+
|
|
649
|
+
expiration_warning = None
|
|
650
|
+
expiration_error = None
|
|
651
|
+
|
|
652
|
+
expiration_warning_days = get_plugin_config(
|
|
653
|
+
"netbox_dns", "zone_expiration_warning_days"
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
if self.expiration_date < date.today():
|
|
657
|
+
expiration_error = _("The registration for this domain has expired.")
|
|
658
|
+
elif (self.expiration_date - date.today()).days < expiration_warning_days:
|
|
659
|
+
expiration_warning = _(
|
|
660
|
+
f"The registration for this domain will expire in less than {expiration_warning_days} days."
|
|
406
661
|
)
|
|
407
662
|
|
|
408
|
-
|
|
409
|
-
ns_warnings.append(
|
|
410
|
-
f"Nameserver {nameserver.name} does not have an active address record in zone {ns_zone}"
|
|
411
|
-
)
|
|
663
|
+
return expiration_warning, expiration_error
|
|
412
664
|
|
|
413
|
-
|
|
665
|
+
def check_soa_serial_increment(self, old_serial, new_serial):
|
|
666
|
+
MAX_SOA_SERIAL_INCREMENT = 2**31 - 1
|
|
667
|
+
SOA_SERIAL_WRAP = 2**32
|
|
668
|
+
|
|
669
|
+
if old_serial is None:
|
|
670
|
+
return
|
|
671
|
+
|
|
672
|
+
if (new_serial - old_serial) % SOA_SERIAL_WRAP > MAX_SOA_SERIAL_INCREMENT:
|
|
673
|
+
raise ValidationError(
|
|
674
|
+
{
|
|
675
|
+
"soa_serial": _(
|
|
676
|
+
"soa_serial must not decrease for zone {zone}."
|
|
677
|
+
).format(zone=self.name)
|
|
678
|
+
}
|
|
679
|
+
)
|
|
414
680
|
|
|
415
681
|
def get_auto_serial(self):
|
|
416
|
-
records =
|
|
417
|
-
type=
|
|
682
|
+
records = Record.objects.filter(zone_id=self.pk).exclude(
|
|
683
|
+
type=RecordTypeChoices.SOA
|
|
418
684
|
)
|
|
419
685
|
if records:
|
|
420
686
|
soa_serial = (
|
|
@@ -430,31 +696,129 @@ class Zone(NetBoxModel):
|
|
|
430
696
|
|
|
431
697
|
return soa_serial
|
|
432
698
|
|
|
433
|
-
def update_serial(self):
|
|
699
|
+
def update_serial(self, save_zone_serial=True):
|
|
700
|
+
if not self.soa_serial_auto:
|
|
701
|
+
return
|
|
702
|
+
|
|
434
703
|
self.last_updated = datetime.now()
|
|
435
704
|
self.soa_serial = ceil(datetime.now().timestamp())
|
|
436
|
-
|
|
437
|
-
|
|
705
|
+
|
|
706
|
+
if save_zone_serial:
|
|
707
|
+
super().save(update_fields=["soa_serial", "last_updated"])
|
|
708
|
+
self.soa_serial_dirty = False
|
|
709
|
+
self.update_soa_record()
|
|
710
|
+
else:
|
|
711
|
+
self.soa_serial_dirty = True
|
|
712
|
+
|
|
713
|
+
def save_soa_serial(self):
|
|
714
|
+
if self.soa_serial_auto and self.soa_serial_dirty:
|
|
715
|
+
super().save(update_fields=["soa_serial", "last_updated"])
|
|
716
|
+
self.soa_serial_dirty = False
|
|
438
717
|
|
|
439
718
|
@property
|
|
440
719
|
def network_from_name(self):
|
|
441
720
|
return arpa_to_prefix(self.name)
|
|
442
721
|
|
|
443
|
-
def
|
|
444
|
-
if self.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
722
|
+
def update_rfc2317_parent_zone(self):
|
|
723
|
+
if not self.is_rfc2317_zone:
|
|
724
|
+
return
|
|
725
|
+
|
|
726
|
+
if self.rfc2317_parent_managed:
|
|
727
|
+
rfc2317_parent_zone = self.get_rfc2317_parent_zone()
|
|
728
|
+
|
|
729
|
+
if rfc2317_parent_zone is None:
|
|
730
|
+
self.rfc2317_parent_managed = False
|
|
731
|
+
self.rfc2317_parent_zone = None
|
|
732
|
+
self.save(
|
|
733
|
+
update_fields=["rfc2317_parent_zone", "rfc2317_parent_managed"]
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
elif self.rfc2317_parent_zone != rfc2317_parent_zone:
|
|
737
|
+
self.rfc2317_parent_zone = rfc2317_parent_zone
|
|
738
|
+
self.save(update_fields=["rfc2317_parent_zone"])
|
|
739
|
+
|
|
740
|
+
ptr_records = self.records.filter(type=RecordTypeChoices.PTR).prefetch_related(
|
|
741
|
+
"zone", "rfc2317_cname_record"
|
|
742
|
+
)
|
|
743
|
+
ptr_zones = {ptr_record.zone for ptr_record in ptr_records}
|
|
744
|
+
|
|
745
|
+
if self.rfc2317_parent_managed:
|
|
746
|
+
for ptr_record in ptr_records:
|
|
747
|
+
ptr_record.save(save_zone_serial=False)
|
|
748
|
+
|
|
749
|
+
self.rfc2317_parent_zone.save_soa_serial()
|
|
750
|
+
self.rfc2317_parent_zone.update_soa_record()
|
|
751
|
+
else:
|
|
752
|
+
cname_records = {
|
|
753
|
+
ptr_record.rfc2317_cname_record
|
|
754
|
+
for ptr_record in ptr_records
|
|
755
|
+
if ptr_record.rfc2317_cname_record is not None
|
|
756
|
+
}
|
|
757
|
+
cname_zones = {cname_record.zone for cname_record in cname_records}
|
|
758
|
+
|
|
759
|
+
for ptr_record in ptr_records:
|
|
760
|
+
ptr_record.save(update_rfc2317_cname=False, save_zone_serial=False)
|
|
761
|
+
for cname_record in cname_records:
|
|
762
|
+
cname_record.delete(save_zone_serial=False)
|
|
763
|
+
|
|
764
|
+
for cname_zone in cname_zones:
|
|
765
|
+
cname_zone.save_soa_serial()
|
|
766
|
+
cname_zone.update_soa_record()
|
|
767
|
+
|
|
768
|
+
for ptr_zone in ptr_zones:
|
|
769
|
+
ptr_zone.save_soa_serial()
|
|
770
|
+
ptr_zone.update_soa_record()
|
|
771
|
+
|
|
772
|
+
def clean_fields(self, exclude=None):
|
|
773
|
+
defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
|
|
774
|
+
|
|
775
|
+
if get_plugin_config("netbox_dns", "convert_names_to_lowercase", False):
|
|
776
|
+
self.name = self.name.lower()
|
|
777
|
+
|
|
778
|
+
if self.view_id is None:
|
|
779
|
+
self.view_id = View.get_default_view().pk
|
|
780
|
+
|
|
781
|
+
for field, value in self.get_defaults().items():
|
|
782
|
+
if getattr(self, field) in (None, ""):
|
|
783
|
+
if value not in (None, ""):
|
|
784
|
+
setattr(self, field, value)
|
|
785
|
+
|
|
786
|
+
if self.soa_mname_id is None and "soa_mname" not in exclude:
|
|
787
|
+
if default_soa_mname := defaults.get("zone_soa_mname"):
|
|
788
|
+
try:
|
|
789
|
+
self.soa_mname = NameServer.objects.get(name=default_soa_mname)
|
|
790
|
+
except NameServer.DoesNotExist:
|
|
791
|
+
raise ValidationError(
|
|
792
|
+
{
|
|
793
|
+
"soa_mname": _(
|
|
794
|
+
"Default soa_mname instance {nameserver} does not exist"
|
|
795
|
+
).format(nameserver=default_soa_mname)
|
|
796
|
+
}
|
|
797
|
+
)
|
|
798
|
+
else:
|
|
450
799
|
raise ValidationError(
|
|
451
800
|
{
|
|
452
|
-
"
|
|
801
|
+
"soa_mname": _(
|
|
802
|
+
"soa_mname not set and no template or default value defined"
|
|
803
|
+
)
|
|
453
804
|
}
|
|
454
805
|
)
|
|
455
806
|
|
|
807
|
+
super().clean_fields(exclude=exclude)
|
|
808
|
+
|
|
456
809
|
def clean(self, *args, **kwargs):
|
|
457
|
-
self.
|
|
810
|
+
if not self.dnssec_policy:
|
|
811
|
+
self.parental_agents = self._meta.get_field("parental_agents").get_default()
|
|
812
|
+
|
|
813
|
+
if not self.registrar:
|
|
814
|
+
self.registry_domain_id = self._meta.get_field(
|
|
815
|
+
"registry_domain_id"
|
|
816
|
+
).get_default()
|
|
817
|
+
self.expiration_date = self._meta.get_field("expiration_date").get_default()
|
|
818
|
+
self.domain_status = self._meta.get_field("domain_status").get_default()
|
|
819
|
+
|
|
820
|
+
if self.soa_ttl is None:
|
|
821
|
+
self.soa_ttl = self.default_ttl
|
|
458
822
|
|
|
459
823
|
try:
|
|
460
824
|
self.name = normalize_name(self.name)
|
|
@@ -463,115 +827,314 @@ class Zone(NetBoxModel):
|
|
|
463
827
|
{
|
|
464
828
|
"name": str(exc),
|
|
465
829
|
}
|
|
466
|
-
)
|
|
830
|
+
)
|
|
467
831
|
|
|
468
832
|
try:
|
|
469
|
-
validate_domain_name(self.name)
|
|
833
|
+
validate_domain_name(self.name, zone_name=True)
|
|
470
834
|
except ValidationError as exc:
|
|
471
835
|
raise ValidationError(
|
|
472
836
|
{
|
|
473
837
|
"name": exc,
|
|
474
838
|
}
|
|
475
|
-
)
|
|
839
|
+
)
|
|
476
840
|
|
|
841
|
+
if self.soa_rname in (None, ""):
|
|
842
|
+
raise ValidationError(
|
|
843
|
+
{
|
|
844
|
+
"soa_rname": _(
|
|
845
|
+
"soa_rname not set and no template or default value defined"
|
|
846
|
+
),
|
|
847
|
+
}
|
|
848
|
+
)
|
|
477
849
|
try:
|
|
478
850
|
dns_name.from_text(self.soa_rname, origin=dns_name.root)
|
|
479
|
-
|
|
851
|
+
validate_rname(self.soa_rname)
|
|
480
852
|
except (DNSException, ValidationError) as exc:
|
|
481
853
|
raise ValidationError(
|
|
482
854
|
{
|
|
483
855
|
"soa_rname": exc,
|
|
484
856
|
}
|
|
485
|
-
) from None
|
|
486
|
-
|
|
487
|
-
if self.soa_serial is None and not self.soa_serial_auto:
|
|
488
|
-
raise ValidationError(
|
|
489
|
-
{
|
|
490
|
-
"soa_serial": f"soa_serial is not defined and soa_serial_auto is disabled for zone {self.name}."
|
|
491
|
-
}
|
|
492
857
|
)
|
|
493
858
|
|
|
494
|
-
|
|
495
|
-
|
|
859
|
+
if not self.soa_serial_auto:
|
|
860
|
+
if self.soa_serial is None:
|
|
861
|
+
raise ValidationError(
|
|
862
|
+
{
|
|
863
|
+
"soa_serial": _(
|
|
864
|
+
"soa_serial is not defined and soa_serial_auto is disabled for zone {zone}."
|
|
865
|
+
).format(zone=self.name)
|
|
866
|
+
}
|
|
867
|
+
)
|
|
496
868
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
869
|
+
if not self._state.adding:
|
|
870
|
+
old_soa_serial = self.get_saved_value("soa_serial")
|
|
871
|
+
old_soa_serial_auto = self.get_saved_value("soa_serial_auto")
|
|
872
|
+
|
|
873
|
+
if not self.soa_serial_auto:
|
|
874
|
+
self.check_soa_serial_increment(old_soa_serial, self.soa_serial)
|
|
875
|
+
elif not old_soa_serial_auto:
|
|
876
|
+
try:
|
|
877
|
+
self.check_soa_serial_increment(
|
|
878
|
+
old_soa_serial, self.get_auto_serial()
|
|
879
|
+
)
|
|
880
|
+
except ValidationError:
|
|
881
|
+
raise ValidationError(
|
|
882
|
+
{
|
|
883
|
+
"soa_serial_auto": _(
|
|
884
|
+
"Enabling soa_serial_auto would decrease soa_serial for zone {zone}."
|
|
885
|
+
).format(zone=self.name)
|
|
886
|
+
}
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
old_name = self.get_saved_value("name")
|
|
890
|
+
old_view_id = self.get_saved_value("view_id")
|
|
500
891
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
892
|
+
if (
|
|
893
|
+
not self.ip_addresses_checked
|
|
894
|
+
and old_name != self.name
|
|
895
|
+
or old_view_id != self.view_id
|
|
896
|
+
):
|
|
897
|
+
ip_addresses = IPAddress.objects.filter(
|
|
898
|
+
netbox_dns_records__in=self.records.filter(
|
|
899
|
+
ipam_ip_address__isnull=False
|
|
900
|
+
)
|
|
901
|
+
)
|
|
902
|
+
ip_addresses |= get_ip_addresses_by_zone(self)
|
|
504
903
|
|
|
505
|
-
|
|
506
|
-
|
|
904
|
+
for ip_address in ip_addresses.distinct():
|
|
905
|
+
try:
|
|
906
|
+
check_dns_records(ip_address, zone=self)
|
|
907
|
+
except ValidationError as exc:
|
|
908
|
+
raise ValidationError(exc.messages)
|
|
909
|
+
|
|
910
|
+
self.ip_addresses_checked = True
|
|
507
911
|
|
|
508
912
|
if self.is_reverse_zone:
|
|
509
913
|
self.arpa_network = self.network_from_name
|
|
510
914
|
|
|
915
|
+
if self.is_rfc2317_zone:
|
|
916
|
+
if self.arpa_network is not None:
|
|
917
|
+
raise ValidationError(
|
|
918
|
+
{
|
|
919
|
+
"rfc2317_prefix": _(
|
|
920
|
+
"A regular reverse zone can not be used as an RFC2317 zone."
|
|
921
|
+
)
|
|
922
|
+
}
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
if self.rfc2317_parent_managed:
|
|
926
|
+
rfc2317_parent_zone = self.get_rfc2317_parent_zone()
|
|
927
|
+
|
|
928
|
+
if rfc2317_parent_zone is None:
|
|
929
|
+
raise ValidationError(
|
|
930
|
+
{
|
|
931
|
+
"rfc2317_parent_managed": _(
|
|
932
|
+
"Parent zone not found in view {view}."
|
|
933
|
+
).format(view=self.view)
|
|
934
|
+
}
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
self.rfc2317_parent_zone = rfc2317_parent_zone
|
|
938
|
+
else:
|
|
939
|
+
self.rfc2317_parent_zone = None
|
|
940
|
+
|
|
941
|
+
overlapping_zones = self.view.zones.filter(
|
|
942
|
+
rfc2317_prefix__net_overlap=self.rfc2317_prefix,
|
|
943
|
+
active=True,
|
|
944
|
+
).exclude(pk=self.pk)
|
|
945
|
+
|
|
946
|
+
if overlapping_zones.exists():
|
|
947
|
+
raise ValidationError(
|
|
948
|
+
{
|
|
949
|
+
"rfc2317_prefix": _(
|
|
950
|
+
"RFC2317 prefix overlaps with zone {zone}."
|
|
951
|
+
).format(zone=overlapping_zones.first())
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
else:
|
|
956
|
+
self.rfc2317_parent_managed = False
|
|
957
|
+
self.rfc2317_parent_zone = None
|
|
958
|
+
|
|
959
|
+
super().clean(*args, **kwargs)
|
|
960
|
+
|
|
961
|
+
def save(self, *args, **kwargs):
|
|
962
|
+
self.full_clean()
|
|
963
|
+
|
|
964
|
+
changed_fields = self.changed_fields
|
|
965
|
+
|
|
966
|
+
if self.soa_serial_auto and (
|
|
967
|
+
changed_fields is None or changed_fields - self.soa_clean_fields
|
|
968
|
+
):
|
|
969
|
+
self.soa_serial = self.get_auto_serial()
|
|
970
|
+
|
|
511
971
|
super().save(*args, **kwargs)
|
|
512
972
|
|
|
513
973
|
if (
|
|
514
|
-
|
|
974
|
+
changed_fields is None or {"name", "view", "status"} & changed_fields
|
|
515
975
|
) and self.is_reverse_zone:
|
|
516
|
-
zones =
|
|
517
|
-
self.
|
|
518
|
-
arpa_network__net_contains_or_equals=self.arpa_network,
|
|
976
|
+
zones = self.view.zones.filter(
|
|
977
|
+
arpa_network__net_contains_or_equals=self.arpa_network
|
|
519
978
|
)
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
979
|
+
|
|
980
|
+
if self.arpa_network.version == IPAddressFamilyChoices.FAMILY_4:
|
|
981
|
+
record_type = RecordTypeChoices.A
|
|
982
|
+
else:
|
|
983
|
+
record_type = RecordTypeChoices.AAAA
|
|
984
|
+
|
|
985
|
+
address_records = Record.objects.filter(
|
|
986
|
+
Q(
|
|
987
|
+
ptr_record__isnull=True,
|
|
988
|
+
zone__view=self.view,
|
|
989
|
+
ip_address__isnull=False,
|
|
990
|
+
ip_address__contained=self.arpa_network,
|
|
991
|
+
type=record_type,
|
|
992
|
+
)
|
|
993
|
+
| Q(ptr_record__zone__in=zones),
|
|
523
994
|
disable_ptr=False,
|
|
524
995
|
)
|
|
996
|
+
|
|
525
997
|
for address_record in address_records:
|
|
526
|
-
address_record.
|
|
998
|
+
address_record.save(
|
|
999
|
+
update_fields=["ptr_record"], save_zone_serial=False
|
|
1000
|
+
)
|
|
527
1001
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
type__in=(record.RecordTypeChoices.A, record.RecordTypeChoices.AAAA)
|
|
531
|
-
):
|
|
532
|
-
address_record.update_ptr_record()
|
|
1002
|
+
for zone in zones:
|
|
1003
|
+
zone.save_soa_serial()
|
|
533
1004
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1005
|
+
if self.arpa_network.version == 4:
|
|
1006
|
+
rfc2317_child_zones = Zone.objects.filter(
|
|
1007
|
+
rfc2317_prefix__net_contained=self.arpa_network,
|
|
1008
|
+
rfc2317_parent_managed=True,
|
|
1009
|
+
)
|
|
1010
|
+
for child_zone in rfc2317_child_zones:
|
|
1011
|
+
child_zone.update_rfc2317_parent_zone()
|
|
1012
|
+
|
|
1013
|
+
if (
|
|
1014
|
+
changed_fields is None
|
|
1015
|
+
or {"name", "view", "status", "rfc2317_prefix", "rfc2317_parent_managed"}
|
|
1016
|
+
& changed_fields
|
|
1017
|
+
) and self.is_rfc2317_zone:
|
|
1018
|
+
zones = self.view.zones.filter(
|
|
1019
|
+
arpa_network__net_contains=self.rfc2317_prefix
|
|
1020
|
+
)
|
|
1021
|
+
address_records = Record.objects.filter(
|
|
1022
|
+
Q(ptr_record__isnull=True, ip_address__contained=self.rfc2317_prefix)
|
|
1023
|
+
| Q(ptr_record__zone__in=zones)
|
|
1024
|
+
| Q(ptr_record__zone=self),
|
|
1025
|
+
type=RecordTypeChoices.A,
|
|
1026
|
+
disable_ptr=False,
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
for address_record in address_records:
|
|
1030
|
+
address_record.save(
|
|
1031
|
+
update_fields=["ptr_record"],
|
|
1032
|
+
update_rfc2317_cname=False,
|
|
1033
|
+
save_zone_serial=False,
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
for zone in zones:
|
|
1037
|
+
zone.save_soa_serial()
|
|
1038
|
+
|
|
1039
|
+
self.update_rfc2317_parent_zone()
|
|
1040
|
+
|
|
1041
|
+
elif changed_fields is not None and {"name", "view", "status"} & changed_fields:
|
|
1042
|
+
for address_record in self.records.filter(
|
|
1043
|
+
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
|
|
1044
|
+
ipam_ip_address__isnull=True,
|
|
538
1045
|
):
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1046
|
+
address_record.save(update_fields=["ptr_record"])
|
|
1047
|
+
|
|
1048
|
+
if changed_fields is not None and "name" in changed_fields:
|
|
1049
|
+
for _record in self.records.filter(ipam_ip_address__isnull=True):
|
|
1050
|
+
_record.save(
|
|
1051
|
+
update_fields=["fqdn"],
|
|
1052
|
+
save_zone_serial=False,
|
|
1053
|
+
update_rrset_ttl=False,
|
|
1054
|
+
update_rfc2317_cname=False,
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
if changed_fields is None or {"name", "view"} & changed_fields:
|
|
1058
|
+
ip_addresses = IPAddress.objects.filter(
|
|
1059
|
+
netbox_dns_records__in=self.records.filter(
|
|
1060
|
+
ipam_ip_address__isnull=False
|
|
1061
|
+
)
|
|
1062
|
+
)
|
|
1063
|
+
ip_addresses |= get_ip_addresses_by_zone(self)
|
|
544
1064
|
|
|
1065
|
+
for ip_address in ip_addresses.distinct():
|
|
1066
|
+
update_dns_records(ip_address)
|
|
1067
|
+
|
|
1068
|
+
self.save_soa_serial()
|
|
545
1069
|
self.update_soa_record()
|
|
546
1070
|
|
|
547
1071
|
def delete(self, *args, **kwargs):
|
|
548
1072
|
with transaction.atomic():
|
|
549
|
-
address_records = self.
|
|
1073
|
+
address_records = self.records.filter(
|
|
1074
|
+
ptr_record__isnull=False
|
|
1075
|
+
).prefetch_related("ptr_record")
|
|
550
1076
|
for address_record in address_records:
|
|
551
1077
|
address_record.ptr_record.delete()
|
|
552
1078
|
|
|
553
|
-
ptr_records = self.
|
|
554
|
-
update_records =
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1079
|
+
ptr_records = self.records.filter(address_records__isnull=False)
|
|
1080
|
+
update_records = list(
|
|
1081
|
+
Record.objects.filter(ptr_record__in=ptr_records).values_list(
|
|
1082
|
+
"pk", flat=True
|
|
1083
|
+
)
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
cname_records = {
|
|
1087
|
+
ptr_record.rfc2317_cname_record
|
|
1088
|
+
for ptr_record in ptr_records
|
|
1089
|
+
if ptr_record.rfc2317_cname_record is not None
|
|
1090
|
+
}
|
|
1091
|
+
cname_zones = {cname_record.zone for cname_record in cname_records}
|
|
1092
|
+
|
|
1093
|
+
for cname_record in cname_records:
|
|
1094
|
+
cname_record.delete(save_zone_serial=False)
|
|
1095
|
+
for cname_zone in cname_zones:
|
|
1096
|
+
cname_zone.save_soa_serial()
|
|
1097
|
+
cname_zone.update_soa_record()
|
|
1098
|
+
|
|
1099
|
+
rfc2317_child_zones = list(
|
|
1100
|
+
self.rfc2317_child_zones.values_list("pk", flat=True)
|
|
1101
|
+
)
|
|
1102
|
+
|
|
1103
|
+
ipam_ip_addresses = list(
|
|
1104
|
+
IPAddress.objects.filter(
|
|
1105
|
+
netbox_dns_records__in=self.records.filter(
|
|
1106
|
+
ipam_ip_address__isnull=False
|
|
1107
|
+
)
|
|
558
1108
|
)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
# Remove coupling from IPAddress to DNS record when zone is deleted
|
|
563
|
-
for ip in IPAddress.objects.filter(
|
|
564
|
-
custom_field_data__ipaddress_dns_zone_id=self.pk
|
|
565
|
-
):
|
|
566
|
-
ip.dns_name = ""
|
|
567
|
-
ip.custom_field_data["ipaddress_dns_record_name"] = None
|
|
568
|
-
ip.custom_field_data["ipaddress_dns_zone_id"] = None
|
|
569
|
-
ip.save(update_fields=["dns_name", "custom_field_data"])
|
|
1109
|
+
.distinct()
|
|
1110
|
+
.values_list("pk", flat=True)
|
|
1111
|
+
)
|
|
570
1112
|
|
|
571
1113
|
super().delete(*args, **kwargs)
|
|
572
1114
|
|
|
573
|
-
|
|
574
|
-
|
|
1115
|
+
address_records = Record.objects.filter(pk__in=update_records).prefetch_related(
|
|
1116
|
+
"zone"
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
for address_record in address_records:
|
|
1120
|
+
address_record.save(save_zone_serial=False)
|
|
1121
|
+
for address_zone in {address_record.zone for address_record in address_records}:
|
|
1122
|
+
address_zone.save_soa_serial()
|
|
1123
|
+
address_zone.update_soa_record()
|
|
1124
|
+
|
|
1125
|
+
ip_addresses = IPAddress.objects.filter(pk__in=ipam_ip_addresses)
|
|
1126
|
+
for ip_address in ip_addresses:
|
|
1127
|
+
update_dns_records(ip_address)
|
|
1128
|
+
|
|
1129
|
+
rfc2317_child_zones = Zone.objects.filter(pk__in=rfc2317_child_zones)
|
|
1130
|
+
if rfc2317_child_zones:
|
|
1131
|
+
for child_zone in rfc2317_child_zones:
|
|
1132
|
+
child_zone.update_rfc2317_parent_zone()
|
|
1133
|
+
|
|
1134
|
+
new_rfc2317_parent_zone = rfc2317_child_zones.first().rfc2317_parent_zone
|
|
1135
|
+
if new_rfc2317_parent_zone is not None:
|
|
1136
|
+
new_rfc2317_parent_zone.save_soa_serial()
|
|
1137
|
+
new_rfc2317_parent_zone.update_soa_record()
|
|
575
1138
|
|
|
576
1139
|
|
|
577
1140
|
@receiver(m2m_changed, sender=Zone.nameservers.through)
|
|
@@ -586,6 +1149,7 @@ def update_ns_records(**kwargs):
|
|
|
586
1149
|
@register_search
|
|
587
1150
|
class ZoneIndex(SearchIndex):
|
|
588
1151
|
model = Zone
|
|
1152
|
+
|
|
589
1153
|
fields = (
|
|
590
1154
|
("name", 100),
|
|
591
1155
|
("view", 150),
|