Пример #1
0
 def test_separate_fqdn_returns_atsign_for_top_of_domain(self):
     name = "%s.%s.%s" % (
         factory.make_name("a"),
         factory.make_name("b"),
         factory.make_name("c"))
     factory.make_Domain(name=name)
     self.assertEqual(('@', name), separate_fqdn(name))
Пример #2
0
    def create(self, request):
        """Create a dnsresourcerecord.

        :param fqdn: Hostname (with domain) for the dnsresource.  Either fqdn
            or (name, domain) must be specified.  Fqdn is ignored if either
            name or domain is given.
        :param name: Hostname (without domain)
        :param domain: Domain (name or id)
        :param rrtype: resource type to create
        :param rrdata: resource data (everything to the right of
            resource type.)
        """
        data = request.data.copy()
        domain = None
        fqdn = data.get('fqdn', None)
        name = data.get('name', None)
        domainname = data.get('domain', None)
        rrtype = data.get('rrtype', None)
        rrdata = data.get('rrdata', None)
        if rrtype is None:
            raise MAASAPIBadRequest("rrtype must be provided.")
        if rrdata is None:
            raise MAASAPIBadRequest("rrdata must be provided.")
        # If the user gave us fqdn and did not give us name/domain, expand
        # fqdn.
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            rrtype = data.get('rrtype', None)
            (name, domainname) = separate_fqdn(fqdn, rrtype)
            data['domain'] = domainname
            data['name'] = name
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NODE_PERMISSION.VIEW)
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname, user=request.user,
                    perm=NODE_PERMISSION.VIEW)
            data['domain'] = domain.id
        if domain is None or name is None:
            raise MAASAPIValidationError(
                "Either name and domain (or fqdn) must be specified")
        # Do we already have a DNSResource for this fqdn?
        dnsrr = DNSResource.objects.filter(name=name, domain__id=domain.id)
        if not dnsrr.exists():
            form = DNSResourceForm(data=data, request=request)
            if form.is_valid():
                form.save()
            else:
                raise MAASAPIValidationError(form.errors)
            dnsrr = DNSResource.objects.filter(name=name, domain__id=domain.id)
        data['dnsresource'] = dnsrr
        form = DNSDataForm(data=data)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Пример #3
0
    def read(self, request):
        """List all resources for the specified criteria.

        :param domain: restrict the listing to entries for the domain.
        :param name: restrict the listing to entries of the given name.
        :param rrtype: restrict the listing to entries which have
            records of the given rrtype.
        """
        data = request.GET
        fqdn = data.get('fqdn', None)
        name = data.get('name', None)
        domainname = data.get('domain', None)
        rrtype = data.get('rrtype', None)
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            (name, domainname) = separate_fqdn(fqdn, rrtype)
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NODE_PERMISSION.VIEW)
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname,
                    user=request.user,
                    perm=NODE_PERMISSION.VIEW)
            query = domain.dnsresource_set.all().order_by('name')
        else:
            query = DNSResource.objects.all().order_by('domain_id', 'name')
        if name is not None:
            query = query.filter(name=name)
        if rrtype is not None:
            query = query.filter(dnsdata__rrtype=rrtype)
        return query
Пример #4
0
 def test_separate_fqdn_allows_domain_override(self):
     parent = "%s.%s" % (factory.make_name("b"), factory.make_name("c"))
     label = "%s.%s" % (factory.make_name("a"), factory.make_name("d"))
     name = "%s.%s" % (label, parent)
     factory.make_Domain(name=parent)
     self.assertEqual((label, parent), separate_fqdn(name,
                                                     domainname=parent))
Пример #5
0
    def read(self, request):
        """List all resources for the specified criteria.

        :param domain: restrict the listing to entries for the domain.
        :param name: restrict the listing to entries of the given name.
        :param rrtype: restrict the listing to entries which have
            records of the given rrtype.
        :param all: if True, also include implicit DNS records created for
            nodes registered in MAAS.
        """
        data = request.GET
        fqdn = data.get('fqdn', None)
        name = data.get('name', None)
        domainname = data.get('domain', None)
        rrtype = data.get('rrtype', None)
        if rrtype is not None:
            rrtype = rrtype.upper()
        _all = get_optional_param(request.GET,
                                  'all',
                                  default=False,
                                  validator=StringBool)
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            (name, domainname) = separate_fqdn(fqdn, rrtype)
        user = request.user
        return get_dnsresource_queryset(_all, domainname, name, rrtype, user)
Пример #6
0
    def create(self, request):
        """@description-title Create a DNS resource
        @description Create a DNS resource.

        @param (string) "fqdn" [required=false] Hostname (with domain) for the
        dnsresource.  Either ``fqdn`` or ``name`` and ``domain`` must be
        specified.  ``fqdn`` is ignored if either ``name`` or ``domain`` is
        given.

        @param (string) "name" [required=true] Hostname (without domain).

        @param (string) "domain" [required=true] Domain (name or id).

        @param (string) "address_ttl" [required=false] Default TTL for entries
        in this zone.

        @param (string) "ip_addresses" [required=false] Address (ip or id) to
        assign to the dnsresource. This creates an A or AAAA record,
        for each of the supplied ip_addresses, IPv4 or IPv6, respectively.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the new DNS
        resource object.
        @success-example "success-json" [exkey=dnsresources-create] placeholder
        text
        """
        data = request.data.copy()
        fqdn = data.get("fqdn", None)
        name = data.get("name", None)
        domainname = data.get("domain", None)
        # If the user gave us fqdn and did not give us name/domain, expand
        # fqdn.
        if domainname is None and name is None and fqdn is not None:
            # Assume that we're working with an address, since we ignore
            # rrtype and rrdata.
            (name, domainname) = separate_fqdn(fqdn, "A")
            data["domain"] = domainname
            data["name"] = name
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NodePermission.view
                )
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname,
                    user=request.user,
                    perm=NodePermission.view,
                )
            data["domain"] = domain.id
        form = DNSResourceForm(data=data, request=request)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Пример #7
0
    def read(self, request):
        """@description-title List all DNS resource records
        @description List all DNS resource records.

        @param (string) "domain" [required=false] Restricts the listing to
        entries for the domain.

        @param (string) "name" [required=false] Restricts the listing to
        entries of the given name.

        @param (string) "rrtype" [required=false] Restricts the listing to
        entries which have records of the given rrtype.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing a list of the
        requested DNS resource record objects.
        @success-example "success-json" [exkey=dnsresourcerecords-read]
        placeholder text
        """
        data = request.GET
        fqdn = data.get("fqdn", None)
        name = data.get("name", None)
        domainname = data.get("domain", None)
        rrtype = data.get("rrtype", None)
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            (name, domainname) = separate_fqdn(fqdn, rrtype)
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NodePermission.view
                )
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname,
                    user=request.user,
                    perm=NodePermission.view,
                )
            query = DNSData.objects.filter(
                dnsresource__domain_id=domain.id
            ).order_by("dnsresource__name")
        else:
            query = DNSData.objects.all().order_by(
                "dnsresource__domain_id", "dnsresource__name"
            )
        if name is not None:
            query = query.filter(dnsresource__name=name)
        if rrtype is not None:
            query = query.filter(rrtype=rrtype)
        return query
Пример #8
0
    def read(self, request):
        """@description-title List resources
        @description List all resources for the specified criteria.

        @param (string) "domain" [required=false] Restricts the listing to
        entries for the domain.

        @param (string) "name" [required=false] Restricts the listing to
        entries of the given name.

        @param (string) "rrtype" [required=false] Restricts the listing to
        entries which have records of the given rrtype.

        @param (boolean) "all" [required=false] Include implicit DNS records
        created for nodes registered in MAAS if true.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing a list of the
        requested DNS resource objects.
        @success-example "success-json" [exkey=dnsresources-read] placeholder
        text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested DNS resources are not found.
        @error-example "not-found"
            Not Found
        """
        data = request.GET
        fqdn = data.get("fqdn", None)
        name = data.get("name", None)
        domainname = data.get("domain", None)
        rrtype = data.get("rrtype", None)
        if rrtype is not None:
            rrtype = rrtype.upper()
        _all = get_optional_param(
            request.GET, "all", default=False, validator=StringBool
        )
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            (name, domainname) = separate_fqdn(fqdn, rrtype)
        user = request.user
        return get_dnsresource_queryset(_all, domainname, name, rrtype, user)
Пример #9
0
    def create(self, request):
        """Create a dnsresource.

        :param fqdn: Hostname (with domain) for the dnsresource.  Either fqdn
            or (name, domain) must be specified.  Fqdn is ignored if either
            name or domain is given.
        :param name: Hostname (without domain)
        :param domain: Domain (name or id)
        :param address_ttl: Default ttl for entries in this zone.
        :param ip_addresses: (optional) Address (ip or id) to assign to the
            dnsresource.
        """
        data = request.data.copy()
        fqdn = data.get('fqdn', None)
        name = data.get('name', None)
        domainname = data.get('domain', None)
        # If the user gave us fqdn and did not give us name/domain, expand
        # fqdn.
        if domainname is None and name is None and fqdn is not None:
            # Assume that we're working with an address, since we ignore
            # rrtype and rrdata.
            (name, domainname) = separate_fqdn(fqdn, 'A')
            data['domain'] = domainname
            data['name'] = name
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NODE_PERMISSION.VIEW)
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname,
                    user=request.user,
                    perm=NODE_PERMISSION.VIEW)
            data['domain'] = domain.id
        form = DNSResourceForm(data=data, request=request)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Пример #10
0
    def create(self, request):
        """Create a DNS resource record.

        :param fqdn: Hostname (with domain) for the dnsresource.  Either fqdn
            or (name, domain) must be specified.  Fqdn is ignored if either
            name or domain is given (e.g. www.your-maas.maas).
        :param name: The name (or hostname without a domain) of the DNS
            resource record (e.g. www.your-maas)
        :param domain: The domain (name or id) where to create the DNS
            resource record (Domain (e.g. 'maas')
        :param rrtype: The resource record type (e.g 'cname', 'mx', 'ns',
            'srv', 'sshfp', 'txt')
        :param rrdata: The resource record data (e.g. 'your-maas',
            '10 mail.your-maas.maas')
        """
        data = request.data.copy()
        domain = None
        fqdn = data.get('fqdn', None)
        name = data.get('name', None)
        domainname = data.get('domain', None)
        rrtype = data.get('rrtype', None)
        rrdata = data.get('rrdata', None)
        if rrtype is None:
            raise MAASAPIBadRequest("rrtype must be provided.")
        if rrdata is None:
            raise MAASAPIBadRequest("rrdata must be provided.")
        # If the user gave us fqdn and did not give us name/domain, expand
        # fqdn.
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            rrtype = data.get('rrtype', None)
            (name, domainname) = separate_fqdn(fqdn, rrtype)
            data['domain'] = domainname
            data['name'] = name
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NodePermission.view)
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname,
                    user=request.user,
                    perm=NodePermission.view)
            data['domain'] = domain.id
        if domain is None or name is None:
            raise MAASAPIValidationError(
                "Either name and domain (or fqdn) must be specified")
        # Do we already have a DNSResource for this fqdn?
        dnsrr = DNSResource.objects.filter(name=name, domain__id=domain.id)
        if not dnsrr.exists():
            form = DNSResourceForm(data=data, request=request)
            if form.is_valid():
                form.save()
            else:
                raise MAASAPIValidationError(form.errors)
            dnsrr = DNSResource.objects.filter(name=name, domain__id=domain.id)
        data['dnsresource'] = dnsrr
        form = DNSDataForm(data=data)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Пример #11
0
    def add_delegations(self, mapping, ns_host_name, dns_ip_list, default_ttl):
        """Find any subdomains that need to be added to this domain, and add
        them.

        This function updates the mapping to add delegations and any needed
        glue records for any domains that are descendants of this one.  These
        are not in the database, because they may be multi-lable (foo.bar.maas
        and maas are domains, but bar.maas isn't), and we don't want to allow
        multi-label elements in the model, due to the extreme complexity it
        introduces.
        """

        # Recursive includes.
        from maasserver.models.dnsresource import separate_fqdn

        subdomains = Domain.objects.filter(name__endswith="." + self.name)
        possible = subdomains[:]
        # Anything with an intervening domain should not be delegated from
        # this domain.
        for middle in possible:
            subdomains = subdomains.exclude(name__endswith="." + middle.name)
        for subdomain in subdomains:
            nsttl = subdomain.get_base_ttl("NS", default_ttl)
            ttl = subdomain.get_base_ttl("A", default_ttl)
            # Strip off this domain name from the end of the resource name.
            name = subdomain.name[:-len(self.name) - 1]
            # If we are authoritative for the subdomain, then generate the NS
            # and any needed glue records.  These will automatically be in the
            # child zone.
            if subdomain.authoritative:
                mapping[name].rrset.add((nsttl, "NS", ns_host_name))
                if ns_host_name.endswith("." + self.name):
                    # The ns_host_name lives in a subdomain of this subdomain,
                    # and we are authoritative for that.  We need to add glue
                    # to this subdomain.
                    ns_name = separate_fqdn(ns_host_name, "NS", self.name)[0]
                    for addr in dns_ip_list:
                        if IPAddress(addr).version == 4:
                            mapping[ns_name].rrset.add((ttl, "A", addr))
                        else:
                            mapping[ns_name].rrset.add((ttl, "AAAA", addr))
            # Also return any NS RRset from the dnsdata for the '@' label in
            # that zone.  Add glue records for NS hosts as needed.
            for lhs in subdomain.dnsresource_set.filter(name="@"):
                for data in lhs.dnsdata_set.filter(rrtype="NS"):
                    mapping[name].rrset.add((ttl, data.rrtype, data.rrdata))
                    # Figure out if we need to add glue, and generate it if
                    # needed.
                    if data.rrdata == "@":
                        # This glue is the responsibility of the admin.
                        continue
                    if not data.rrdata.endswith("."):
                        # Non-qualified NSRR, append the domain.
                        fqdn = "%s.%s." % (data.rrdata, subdomain.name)
                    elif not data.rrdata.endswith("%s." % subdomain.name):
                        continue
                    else:
                        # NSRR is an FQDN in or under subdomain.
                        fqdn = data.rrdata
                    # If we get here, then the NS host is in subdomain, or some
                    # subdomain thereof, and is not '@' in the subdomain.
                    # Strip the trailing dot, and split the FQDN.
                    h_name, d_name = separate_fqdn(fqdn[:-1], "NS")
                    # Make sure we look in the right domain for the addresses.
                    if d_name == subdomain.name:
                        nsrrset = subdomain.dnsresource_set.filter(name=h_name)
                    else:
                        nsdomain = Domain.objects.filter(name=d_name)
                        if not nsdomain.exists():
                            continue
                        else:
                            nsdomain = nsdomain[0]
                        nsrrset = nsdomain.dnsresource_set.filter(name=h_name)
                        h_name = fqdn[:-len(subdomain.name) - 2]
                    for nsrr in nsrrset:
                        for addr in nsrr.get_addresses():
                            if IPAddress(addr).version == 4:
                                mapping[h_name].rrset.add((ttl, "A", addr))
                            else:
                                mapping[h_name].rrset.add((ttl, "AAAA", addr))
Пример #12
0
    def create(self, request):
        """@description-title Create a DNS resource record
        @description Create a new DNS resource record.

        @param (string) "fqdn" [required=false] Hostname (with domain) for the
        dnsresource.  Either ``fqdn`` or ``name`` and  ``domain`` must be
        specified.  ``fqdn`` is ignored if either name or domain is given (e.g.
        www.your-maas.maas).

        @param (string) "name" [required=false] The name (or hostname without a
        domain) of the DNS resource record (e.g. www.your-maas)

        @param (string) "domain" [required=false] The domain (name or id) where
        to create the DNS resource record (Domain (e.g. 'maas')

        @param (string) "rrtype" [required=false] The resource record type (e.g
        ``cname``, ``mx``, ``ns``, ``srv``, ``sshfp``, ``txt``).

        @param (string) "rrdata" [required=false] The resource record data
        (e.g. 'your-maas', '10 mail.your-maas.maas')

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the new DNS
        resource record object.
        @success-example "success-json" [exkey=dnsresourcerecords-create]
        placeholder text
        """
        data = request.data.copy()
        domain = None
        fqdn = data.get("fqdn", None)
        name = data.get("name", None)
        domainname = data.get("domain", None)
        rrtype = data.get("rrtype", None)
        rrdata = data.get("rrdata", None)
        if rrtype is None:
            raise MAASAPIBadRequest("rrtype must be provided.")
        if rrdata is None:
            raise MAASAPIBadRequest("rrdata must be provided.")
        # If the user gave us fqdn and did not give us name/domain, expand
        # fqdn.
        if domainname is None and name is None and fqdn is not None:
            # We need a type for resource separation.  If the user didn't give
            # us a rrtype, then assume it's an address of some sort.
            rrtype = data.get("rrtype", None)
            (name, domainname) = separate_fqdn(fqdn, rrtype)
            data["domain"] = domainname
            data["name"] = name
        # If the domain is a name, make it an id.
        if domainname is not None:
            if domainname.isdigit():
                domain = Domain.objects.get_domain_or_404(
                    domainname, user=request.user, perm=NodePermission.view
                )
            else:
                domain = Domain.objects.get_domain_or_404(
                    "name:%s" % domainname,
                    user=request.user,
                    perm=NodePermission.view,
                )
            data["domain"] = domain.id
        if domain is None or name is None:
            raise MAASAPIValidationError(
                "Either name and domain (or fqdn) must be specified"
            )
        # Do we already have a DNSResource for this fqdn?
        dnsrr = DNSResource.objects.filter(name=name, domain__id=domain.id)
        if not dnsrr.exists():
            form = DNSResourceForm(data=data, request=request)
            if form.is_valid():
                form.save()
            else:
                raise MAASAPIValidationError(form.errors)
            dnsrr = DNSResource.objects.filter(name=name, domain__id=domain.id)
        data["dnsresource"] = dnsrr
        form = DNSDataForm(data=data)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Пример #13
0
 def test_separate_fqdn_splits_nonsrv(self):
     self.assertEqual(
         ("foo", "test.example.com"),
         separate_fqdn("foo.test.example.com", 'A'))
Пример #14
0
 def test_separate_fqdn_splits_srv(self):
     self.assertEqual(
         ("_sip._tcp.voip", "example.com"),
         separate_fqdn("_sip._tcp.voip.example.com", 'SRV'))
Пример #15
0
    def _gen_forward_zones(domains, serial, ns_host_name, mappings,
                           rrset_mappings, default_ttl):
        """Generator of forward zones, collated by domain name."""
        dns_ip_list = get_dns_server_addresses()
        domains = set(domains)

        # For each of the domains that we are generating, create the zone from:
        # 1. Node: ip mapping(domain) (which includes dnsresource addresses).
        # 2. Dnsresource non-address records in this domain.
        # 3. For the default domain all forward look ups for the managed and
        #    unmanaged dynamic ranges.
        for domain in domains:
            zone_ttl = default_ttl if domain.ttl is None else domain.ttl
            # 1. node: ip mapping(domain)
            # Map all of the nodes in this domain, including the user-reserved
            # ip addresses.  Separate_fqdn handles top-of-domain names needing
            # to have the name '@', and we already know the domain name, so we
            # discard that part of the return.
            mapping = {
                separate_fqdn(hostname, domainname=domain.name)[0]: info
                for hostname, info in mappings[domain].items()
            }
            # 2a. Create non-address records.  Specifically ignore any CNAME
            # records that collide with addresses in mapping.
            other_mapping = rrset_mappings[domain]

            # 2b. Capture NS RRsets for anything that is a child of this domain
            domain.add_delegations(other_mapping, ns_host_name, dns_ip_list,
                                   default_ttl)

            # 3. All of the special handling for the default domain.
            dynamic_ranges = []
            if domain.is_default():
                # 3a. All forward entries for the managed and unmanaged dynamic
                # ranges go into the default domain.
                subnets = Subnet.objects.all().prefetch_related("iprange_set")
                for subnet in subnets:
                    # We loop through the whole set so the prefetch above works
                    # in one query.
                    for ip_range in subnet.iprange_set.all():
                        if ip_range.type == IPRANGE_TYPE.DYNAMIC:
                            dynamic_ranges.append(ip_range.get_MAASIPRange())
                # 3b. Add A/AAAA RRset for @.  If glue is needed for any other
                # domain, adding the glue is the responsibility of the admin.
                ttl = domain.get_base_ttl('A', default_ttl)
                for dns_ip in dns_ip_list:
                    if dns_ip.version == 4:
                        other_mapping['@'].rrset.add(
                            (ttl, 'A', dns_ip.format()))
                    else:
                        other_mapping['@'].rrset.add(
                            (ttl, 'AAAA', dns_ip.format()))

            yield DNSForwardZoneConfig(
                domain.name,
                serial=serial,
                default_ttl=zone_ttl,
                ns_ttl=domain.get_base_ttl('NS', default_ttl),
                ipv4_ttl=domain.get_base_ttl('A', default_ttl),
                ipv6_ttl=domain.get_base_ttl('AAAA', default_ttl),
                mapping=mapping,
                ns_host_name=ns_host_name,
                other_mapping=other_mapping,
                dynamic_ranges=dynamic_ranges,
            )