Beispiel #1
0
class DNSZoneSerializer(NeedsReviewMixin, BonkTriggerMixin, HistorySerializerMixin):
    id = serializers.CharField(required=False)
    tags = serializers.DictField(required=False)
    needs_review = serializers.BooleanField(required=False, default=False)
    type = serializers.ChoiceField(required=True, choices=['internal', 'external'])
    name = serializers.CharField(required=True, validators=[validate_fqdn])
    soa = DNSSOASerializer(required=False)
    ttl = serializers.IntegerField(required=False, validators=[validate_ttl])
    options = DNSZoneOptionsSerializer(required=False)
    permissions = PermissionsSerializer(required=False)

    class Meta(RethinkSerializer.Meta):
        table_name = 'dns_zone'
        slug_field = 'name'
        needs_review_field = 'needs_review'
        indices = [
            'name',
            ('permissions_read', r.row['permissions']['read'], {'multi': True}),
            ('permissions_create', r.row['permissions']['create'], {'multi': True}),
            ('permissions_write', r.row['permissions']['write'], {'multi': True}),
        ]
        unique = [
            'name',
        ]

    def create_link(self, instance):
        return reverse('bonk:zone_detail', kwargs={'slug': instance['name']}, request=self.context.get('request'))

    def validate_name(self, value):
        if self.instance is not None and self.instance['name'] != value:
            for record in DNSRecordSerializer.filter(zone=self.instance['name']):
                raise serializers.ValidationError(
                    "cannot modify the name of a zone with records"
                )
        return value

    def validate(self, data):
        data = super(DNSZoneSerializer, self).validate(data)
        if (self.instance is None and
                self.context['request'].user is not None
                and not self.context['request'].user.is_superuser
            ):
            zones = DNSZoneSerializer.filter(type=data['type'])
            parent = {'name': '.'}
            for zone in zones:
                if data['name'].endswith("." + zone['name']):
                    if len(zone['name']) > len(parent['name']):
                        parent = zone
            allowed = set(
                parent.get('permissions', {}).get('write', [])
                + parent.get('permissions', {}).get('create', [])
            )
            groups = set(self.context['request'].user.groups.all().values_list('name', flat=True))
            if len(groups.intersection(allowed)) == 0:
                raise serializers.ValidationError("you do not have permissions to zone %s" % (parent['name']))
        return data
Beispiel #2
0
class IPBlockSerializer(HistorySerializerMixin):
    id = serializers.CharField(required=False)
    tags = serializers.DictField(required=False)
    vrf = serializers.IntegerField(required=True, validators=[validate_vrf])
    network = serializers.IPAddressField(required=True)
    length = serializers.IntegerField(required=True)
    announced_by = serializers.CharField(required=False)
    permissions = PermissionsSerializer(required=False)

    class Meta(RethinkSerializer.Meta):
        table_name = 'ip_block'
        slug_field = 'vrf_network_length'
        indices = [
            ('vrf_network_length', (r.row['vrf'], r.row['network'], r.row['length'])),
            ('permissions_read', r.row['permissions']['read'], {'multi': True}),
            ('permissions_create', r.row['permissions']['create'], {'multi': True}),
            ('permissions_write', r.row['permissions']['write'], {'multi': True}),
        ]
        unique_together = [
            ('vrf', 'network', 'length'),
        ]

    @classmethod
    def get_by_ip(cls, vrf, ip, reql=False):
        query = cls.filter(lambda b:
                    r.ip_prefix_contains(
                        r.ip_prefix(b['network'], b['length']),
                        r.ip_address(ip)
                    ), reql=True) \
                .filter({'vrf': vrf}) \
                .order_by(r.desc("length")).nth(0)
        if reql:
            return query
        else:
            try:
                return query.run(get_connection())
            except r.errors.ReqlNonExistenceError:
                raise RethinkObjectNotFound("no block found for IP %s" % ip)

    @classmethod
    def filter_by_block(cls, block, reql=False):
        return cls.filter(lambda b:
            r.ip_prefix_contains(
                r.ip_prefix(block['network'], block['length']),
                r.ip_address(b['network'])
            ), reql=reql)

    def validate(self, data):
        data = super(IPBlockSerializer, self).validate(data)
        full = self.get_updated_object(data)
        network = netaddr.IPNetwork("%s/%d" % (full['network'], full['length']))
        if str(network.network) != full['network']:
            raise serializers.ValidationError("network is not the network address for %s/%d" % (full['network'], full['length']))
        return data
Beispiel #3
0
class DNSRecordSerializer(NeedsReviewMixin, BonkTriggerMixin,
                          HistorySerializerMixin):
    id = serializers.CharField(required=False)
    name = serializers.CharField(required=True, validators=[validate_fqdn])
    zone = serializers.CharField(required=True)
    type = serializers.ChoiceField(
        choices=['A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SRV', 'TXT'],
        required=True)
    ttl = serializers.IntegerField(required=False, validators=[validate_ttl])
    value = serializers.ListField(child=serializers.CharField())
    reference = serializers.CharField(required=False)
    permissions = PermissionsSerializer(required=False)

    class Meta(RethinkSerializer.Meta):
        table_name = 'dns_record'
        slug_field = 'name_type'
        indices = [
            'name',
            'zone',
            ('name_type', (r.row['name'], r.row['type'])),
            ('zone_name_type', (r.row['zone'], r.row['name'], r.row['type'])),
            ('value', {
                'multi': True
            }),
            ('permissions_read', r.row['permissions']['read'], {
                'multi': True
            }),
            ('permissions_create', r.row['permissions']['create'], {
                'multi': True
            }),
            ('permissions_write', r.row['permissions']['write'], {
                'multi': True
            }),
        ]
        unique_together = [
            ('zone', 'name', 'type'),
        ]

    def needs_review(self, instance, data):
        if not hasattr(self, 'zone'):
            self._zone = DNSZoneSerializer(
                DNSZoneSerializer.get(name=data.get(
                    'zone', instance['zone'] if instance else None)))
        return self._zone.needs_review(self._zone.instance, {})

    def get_reviewers(self, instance, data):
        return self._zone.get_reviewers(self._zone.instance, {})

    def validate_zone(self, value):
        try:
            self._zone = DNSZoneSerializer(DNSZoneSerializer.get(name=value))
        except RethinkObjectNotFound:
            raise serializers.ValidationError("'%s' does not exist" % value)
        if 'request' in self.context and not self.context[
                'request'].user.is_superuser:
            user_groups = set(
                self.context['request'].user.groups.all().values_list(
                    'name', flat=True))
            if self.instance is not None and len(
                    user_groups.intersection(
                        set(
                            self.instance.get('permissions', {}).get(
                                'write', [])))) > 0:
                pass
            elif len(
                    user_groups.intersection(
                        set(
                            self._zone.instance.get('permissions', {}).get(
                                'create', []) +
                            self._zone.instance.get('permissions', {}).get(
                                'write', [])))) == 0:
                raise serializers.ValidationError(
                    "you do not have permission to create names in %s" % value)
        return value

    def validate(self, data):
        data = super(DNSRecordSerializer, self).validate(data)
        full = self.get_updated_object(data)
        if full['name'] != full['zone'] and not full['name'].endswith(
                '.' + full['zone']):
            raise serializers.ValidationError("name %s is not in zone %s" %
                                              (full['name'], full['zone']))
        # FIXME: Add validation of value for type
        if full['type'] == 'CNAME':
            records = list(DNSRecordSerializer.filter(name=full['name']))
            if self.instance is not None:
                records = filter(lambda x: x['id'] != self.instance['id'],
                                 records)
            if len(records) > 0:
                raise serializers.ValidationError(
                    "a CNAME record cannot be used on a name with any other record type"
                )
            if len(full['value']) > 1:
                raise serializers.ValidationError(
                    "a CNAME record can only have one value")
        else:
            records = list(
                DNSRecordSerializer.filter(name=full['name'], type='CNAME'))
            if self.instance is not None:
                records = filter(lambda x: x['id'] != self.instance['id'],
                                 records)
            if len(records) > 0:
                raise serializers.ValidationError(
                    "a CNAME record exists for the specified name already")
        return data
Beispiel #4
0
class IPAddressSerializer(BonkTriggerMixin, HistorySerializerMixin):
    id = serializers.CharField(required=False)
    tags = serializers.DictField(required=False)
    state = serializers.ChoiceField(
        required=True, choices=['allocated', 'reserved', 'quarantine'])
    vrf = serializers.IntegerField(required=True, validators=[validate_vrf])
    ip = serializers.IPAddressField(required=True)
    name = serializers.CharField(required=True, validators=[validate_fqdn])
    dhcp_mac = serializers.ListField(
        child=serializers.CharField(validators=[validate_mac]), required=False)
    reference = serializers.CharField(required=False)
    permissions = PermissionsSerializer(required=False)
    ttl = serializers.IntegerField(required=False, validators=[validate_ttl])

    class Meta(RethinkSerializer.Meta):
        table_name = 'ip_address'
        slug_field = 'vrf_ip'
        indices = [
            'ip',
            'name',
            ('vrf_ip', (r.row['vrf'], r.row['ip'])),
            ('permissions_read', r.row['permissions']['read'], {
                'multi': True
            }),
            ('permissions_create', r.row['permissions']['create'], {
                'multi': True
            }),
            ('permissions_write', r.row['permissions']['write'], {
                'multi': True
            }),
        ]
        unique_together = [
            ('vrf', 'ip'),
        ]

    @classmethod
    def filter_by_prefix(cls, prefix, reql=False):
        return cls.filter(lambda a: r.ip_prefix_contains(
            r.ip_prefix(prefix['network'], prefix['length']),
            r.ip_address(a['ip'])),
                          reql=reql)

    def validate_name(self, value):
        possibles = []
        for part in value.split(".")[:0:-1]:
            suffix = "" if len(possibles) == 0 else ("." + possibles[-1])
            possibles.append(part + suffix)
        try:
            zone = DNSZoneSerializer.filter(
                lambda zone: r.expr(possibles).contains(zone['name']),
                reql=True).order_by(r.desc(r.row['name'].count())).nth(0).run(
                    self.conn)
        except r.errors.ReqlNonExistenceError:
            raise serializers.ValidationError(
                "no zone matching %s could be found" % value)
        if 'request' in self.context and not self.context[
                'request'].user.is_superuser:
            user_groups = set(
                self.context['request'].user.groups.all().values_list(
                    'name', flat=True))
            if self.instance is not None and len(
                    user_groups.intersection(
                        set(
                            self.instance.get('permissions', {}).get(
                                'write', [])))) > 0:
                pass
            elif len(
                    user_groups.intersection(
                        set(
                            zone.get('permissions', {}).get('create', []) +
                            zone.get('permissions', {}).get('write', [])))
            ) == 0:
                raise serializers.ValidationError(
                    "you do not have permission to create names in %s" %
                    zone['name'])
        try:
            ip_address = IPAddressSerializer.get(name=value)
            if self.instance is None or ip_address['id'] != self.instance['id']:
                raise serializers.ValidationError(
                    "%r is already in use by %s" % (value, ip_address['ip']))
        except RethinkObjectNotFound:
            pass
        return value

    def validate(self, data):
        data = super(IPAddressSerializer, self).validate(data)
        full = self.get_updated_object(data)
        try:
            prefix = IPPrefixSerializer.get_by_ip(full['vrf'], full['ip'])
        except RethinkObjectNotFound:
            raise serializers.ValidationError("no prefix found for IP %s" %
                                              full['ip'])
        return data
Beispiel #5
0
class IPPrefixSerializer(BonkTriggerMixin, HistorySerializerMixin):
    id = serializers.CharField(required=False)
    tags = serializers.DictField(required=False)
    vrf = serializers.IntegerField(required=True, validators=[validate_vrf])
    network = serializers.IPAddressField(required=True)
    length = serializers.IntegerField(required=True)
    asn = serializers.IntegerField(required=False)
    name = serializers.CharField(required=False)
    state = serializers.ChoiceField(
        required=True, choices=['allocated', 'reserved', 'quarantine'])
    permissions = PermissionsSerializer(required=False)
    gateway = serializers.IPAddressField(required=False)
    dhcp = IPPrefixDHCPSerializer(required=False)
    ddns = IPPrefixDDNSSerializer(required=False)
    reference = serializers.CharField(required=False)
    inventory_id = serializers.CharField(required=False)

    class Meta(RethinkSerializer.Meta):
        table_name = 'ip_prefix'
        slug_field = 'vrf_network_length'
        indices = [
            'name',
            ('vrf_network_length', (r.row['vrf'], r.row['network'],
                                    r.row['length'])),
            ('permissions_read', r.row['permissions']['read'], {
                'multi': True
            }),
            ('permissions_create', r.row['permissions']['create'], {
                'multi': True
            }),
            ('permissions_write', r.row['permissions']['write'], {
                'multi': True
            }),
        ]
        unique = ['name']
        unique_together = [
            ('vrf', 'network', 'length'),
        ]

    @classmethod
    def filter_by_block(cls, block, reql=False):
        return cls.filter(lambda p: r.ip_prefix_contains(
            r.ip_prefix(block['network'], block['length']),
            r.ip_address(p['network'])),
                          reql=reql)

    @classmethod
    def get_by_ip(cls, vrf, ip, reql=False):
        query = cls.filter(lambda p:
                r.ip_prefix_contains(
                    r.ip_prefix(p['network'], p['length']),
                    r.ip_address(ip)
                ), reql=True) \
                .filter({'vrf': vrf}) \
                .order_by(r.desc("length")).nth(0)
        if reql:
            return query
        else:
            try:
                return query.run(get_connection())
            except r.errors.ReqlNonExistenceError:
                raise RethinkObjectNotFound("no prefix found for IP %s" % ip)

    def validate(self, data):
        data = super(IPPrefixSerializer, self).validate(data)
        full = self.get_updated_object(data)
        try:
            block = IPBlockSerializer.get_by_ip(full['vrf'], full['network'])
        except RethinkObjectNotFound:
            raise serializers.ValidationError(
                "no block exists matching prefix %s/%d" %
                (full['network'], full['length']))
        if block['length'] > full['length']:
            raise serializers.ValidationError(
                "prefix %s/%d exceeds block of %s/%d" %
                (full['network'], full['length'], block['network'],
                 block['length']))
        if (self.instance is None and self.context['request'].user is not None
                and not self.context['request'].user.is_superuser):
            allowed = set(
                block.get('permissions', {}).get('write', []) +
                block.get('permissions', {}).get('create', []))
            groups = set(self.context['request'].user.groups.all().values_list(
                'name', flat=True))
            if len(groups.intersection(allowed)) == 0:
                raise serializers.ValidationError(
                    "you do not have permissions to block %s/%d" %
                    (block['network'], block['length']))
        underlappers = filter(
            lambda x: x[self.Meta.pk_field] != full.get(
                self.Meta.pk_field, None), self.filter_by_block(full))
        if len(underlappers) > 0:
            raise serializers.ValidationError(
                "prefix %s/%d overlaps with %r" %
                (full['network'], full['length'], underlappers))
        try:
            overlapper = IPPrefixSerializer.get_by_ip(full['vrf'],
                                                      full['network'])
            if overlapper[self.Meta.pk_field] != full.get(
                    self.Meta.pk_field, None):
                raise serializers.ValidationError(
                    "prefix %s/%d includes this prefix %s/%d" %
                    (overlapper['network'], overlapper['length'],
                     full['network'], full['length']))
        except RethinkObjectNotFound:
            pass
        network = netaddr.IPNetwork("%s/%d" %
                                    (full['network'], full['length']))
        if str(network.network) != full['network']:
            raise serializers.ValidationError(
                "network is not the network address for %s/%d" %
                (full['network'], full['length']))
        return data

    def create(self, data):
        import bonk.tasks
        data = super(IPPrefixSerializer, self).create(data)
        block = IPBlockSerializer.get_by_ip(data['vrf'], data['network'])
        if 'announced_by' in block and data['state'] == 'allocated':
            bonk.tasks.trigger_prefix_create.apply_async((data, block))
        return data

    def update(self, instance, data):
        import bonk.tasks
        ret = super(IPPrefixSerializer, self).update(instance, data)
        block = IPBlockSerializer.get_by_ip(ret['vrf'], ret['network'])
        if 'announced_by' in block:
            if instance['state'] != 'allocated' and ret['state'] == 'allocated':
                bonk.tasks.trigger_prefix_create.apply_async((ret, block))
            elif instance[
                    'state'] == 'allocated' and ret['state'] != 'allocated':
                bonk.tasks.trigger_prefix_delete.apply_async((ret, block))
        return ret

    def delete(self):
        import bonk.tasks
        block = IPBlockSerializer.get_by_ip(self.instance['vrf'],
                                            self.instance['network'])
        if 'announced_by' in block and self.instance['state'] == 'allocated':
            bonk.tasks.trigger_prefix_delete.apply_async(
                (self.instance, block))
        ret = super(IPPrefixSerializer, self).delete()
        return ret
Beispiel #6
0
class DNSRecordSerializer(NeedsReviewMixin, BonkTriggerMixin,
                          HistorySerializerMixin):
    id = serializers.CharField(required=False)
    tags = serializers.DictField(required=False)
    name = serializers.CharField(required=True, validators=[validate_fqdn])
    zone = serializers.CharField(required=True)
    type = serializers.ChoiceField(choices=[
        'A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SRV', 'TXT', 'CAA', 'ANAME'
    ],
                                   required=True)
    ttl = serializers.IntegerField(required=False, validators=[validate_ttl])
    value = serializers.ListField(child=serializers.CharField())
    reference = serializers.CharField(required=False)
    permissions = PermissionsSerializer(required=False)

    class Meta(RethinkSerializer.Meta):
        table_name = 'dns_record'
        slug_field = 'name_type'
        indices = [
            'name',
            'zone',
            ('name_type', (r.row['name'], r.row['type'])),
            ('zone_name_type', (r.row['zone'], r.row['name'], r.row['type'])),
            ('value', {
                'multi': True
            }),
            ('permissions_read', r.row['permissions']['read'], {
                'multi': True
            }),
            ('permissions_create', r.row['permissions']['create'], {
                'multi': True
            }),
            ('permissions_write', r.row['permissions']['write'], {
                'multi': True
            }),
        ]
        unique_together = [
            ('zone', 'name', 'type'),
        ]

    def create_link(self, instance):
        return reverse('bonk:record_detail',
                       kwargs={
                           'name': instance['name'],
                           'type': instance['type'],
                       },
                       request=self.context.get('request'))

    def needs_review(self, instance, data):
        if not hasattr(self, '_zone'):
            self._zone = DNSZoneSerializer(
                DNSZoneSerializer.get(name=data.get(
                    'zone', instance['zone'] if instance else None)))
        return self._zone.needs_review(self._zone.instance, {})

    def get_reviewers(self, instance, data):
        reviewers = self._zone.get_reviewers(self._zone.instance, {})
        if instance is not None:
            reviewers.extend(
                super(DNSRecordSerializer, self).get_reviewers(instance, data))
        return reviewers

    def validate_zone(self, value):
        try:
            self._zone = DNSZoneSerializer(DNSZoneSerializer.get(name=value))
        except RethinkObjectNotFound:
            raise serializers.ValidationError("'%s' does not exist" % value)
        if 'request' in self.context and not self.context[
                'request'].user.is_superuser:
            user_groups = set(
                self.context['request'].user.groups.all().values_list(
                    'name', flat=True))
            if self.instance is not None and len(
                    user_groups.intersection(
                        set(
                            self.instance.get('permissions', {}).get(
                                'write', [])))) > 0:
                pass
            elif len(
                    user_groups.intersection(
                        set(
                            self._zone.instance.get('permissions', {}).get(
                                'create', []) +
                            self._zone.instance.get('permissions', {}).get(
                                'write', [])))) == 0:
                raise serializers.ValidationError(
                    "you do not have permission to create names in %s" % value)
        return value

    def validate(self, data):
        data = super(DNSRecordSerializer, self).validate(data)
        full = self.get_updated_object(data)
        if full['name'] != full['zone'] and not full['name'].endswith(
                '.' + full['zone']):
            raise serializers.ValidationError("name %s is not in zone %s" %
                                              (full['name'], full['zone']))
        if full['type'] == 'CNAME':
            records = list(DNSRecordSerializer.filter(name=full['name']))
            if self.instance is not None:
                records = [
                    x for x in records if x['id'] != self.instance['id']
                ]
            if len(records) > 0:
                raise serializers.ValidationError(
                    "a CNAME record cannot be used on a name with any other record type"
                )
            if len(full['value']) > 1:
                raise serializers.ValidationError(
                    "a CNAME record can only have one value")
            addresses = list(IPAddressSerializer.filter(name=full['name']))
            if len(addresses) > 0:
                raise serializers.ValidationError(
                    "an address with the same name already exists")
        else:
            records = list(
                DNSRecordSerializer.filter(name=full['name'], type='CNAME'))
            if self.instance is not None:
                records = [
                    x for x in records if x['id'] != self.instance['id']
                ]
            if len(records) > 0:
                raise serializers.ValidationError(
                    "a CNAME record exists for the specified name already")
        try:
            dns.zone.from_text("\n".join([
                "%s. %d IN %s %s" % (full['name'], full.get(
                    'ttl', 86400), full['type'].replace("ANAME", "CNAME"), v)
                for v in full['value']
            ]),
                               origin=full['zone'],
                               check_origin=False)
        except dns.exception.SyntaxError:
            raise serializers.ValidationError("value is invalid")
        return data