diff --git a/api/serializers.py b/api/serializers.py index 51afd468..c8cdffd9 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -222,6 +222,13 @@ class SrvSerializer(NamespacedHMSerializer): fields = ('service', 'protocole', 'extension', 'ttl', 'priority', 'weight', 'port', 'target', 'api_url') +class SshFpSerializer(NamespacedHMSerializer): + """Serialize `machines.models.SSHFP` objects. + """ + class Meta: + model = machines.SshFp + field = ('machine', 'pub_key_entry', 'algo', 'comment', 'api_url') + class InterfaceSerializer(NamespacedHMSerializer): """Serialize `machines.models.Interface` objects. @@ -679,6 +686,26 @@ class SRVRecordSerializer(SrvSerializer): fields = ('service', 'protocole', 'ttl', 'priority', 'weight', 'port', 'target') +class SSHFPRecordSerializer(SshFpSerializer): + """Serialize `machines.models.SshFp` objects with the data needed to + generate a SSHFP DNS record. + """ + class Meta(SshFpSerializer.Meta): + fields = ('algo_id', 'hash') + + +class SSHFPInterfaceSerializer(serializers.ModelSerializer): + """Serialize `machines.models.Domain` objects with the data needed to + generate a CNAME DNS record. + """ + hostname = serializers.CharField(source='domain.name', read_only=True) + sshfp = SSHFPRecordSerializer(source='machine.sshfp_set', many=True, read_only=True) + + class Meta: + model = machines.Interface + fields = ('hostname', 'sshfp') + + class ARecordSerializer(serializers.ModelSerializer): """Serialize `machines.models.Interface` objects with the data needed to generate a A DNS record. @@ -716,21 +743,6 @@ class CNAMERecordSerializer(serializers.ModelSerializer): fields = ('alias', 'hostname', 'extension') -class SSHFPRRecordSerializer(serializers.ModelSerializer): - class Meta: - model = machines.SshFingerprint - fields = ('algo_id', 'hash') - - -class SSHFPRInterfaceSerializer(serializers.ModelSerializer): - hostname = serializers.CharField(source='domain.name', read_only=True) - sshfpr = SSHFPRRecordSerializer(source='machine.sshfingerprint_set', many=True, read_only=True) - - class Meta: - model = machines.Interface - fields = ('hostname', 'sshfpr') - - class DNSZonesSerializer(serializers.ModelSerializer): """Serialize the data about DNS Zones. """ @@ -744,13 +756,13 @@ class DNSZonesSerializer(serializers.ModelSerializer): a_records = ARecordSerializer(many=True, source='get_associated_a_records') aaaa_records = AAAARecordSerializer(many=True, source='get_associated_aaaa_records') cname_records = CNAMERecordSerializer(many=True, source='get_associated_cname_records') - sshfpr_records = SSHFPRInterfaceSerializer(many=True, source='get_associated_sshfpr') + sshfp_records = SSHFPInterfaceSerializer(many=True, source='get_associated_sshfp_records') class Meta: model = machines.Extension fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6', 'mx_records', 'txt_records', 'srv_records', 'a_records', - 'aaaa_records', 'cname_records', 'sshfpr_records') + 'aaaa_records', 'cname_records', 'sshfp_records') # MAILING diff --git a/api/urls.py b/api/urls.py index 2947850e..67302789 100644 --- a/api/urls.py +++ b/api/urls.py @@ -54,6 +54,7 @@ router.register_viewset(r'machines/ns', views.NsViewSet) router.register_viewset(r'machines/txt', views.TxtViewSet) router.register_viewset(r'machines/dname', views.DNameViewSet) router.register_viewset(r'machines/srv', views.SrvViewSet) +router.register_viewset(r'machines/sshfp', views.SshFpViewSet) router.register_viewset(r'machines/interface', views.InterfaceViewSet) router.register_viewset(r'machines/ipv6list', views.Ipv6ListViewSet) router.register_viewset(r'machines/domain', views.DomainViewSet) diff --git a/api/views.py b/api/views.py index 45e083cc..7b01b0c3 100644 --- a/api/views.py +++ b/api/views.py @@ -177,6 +177,13 @@ class SrvViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.SrvSerializer +class SshFpViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.SshFp` objects. + """ + queryset = machines.SshFp.objects.all() + serializer_class = serializers.SshFpSerializer + + class InterfaceViewSet(viewsets.ReadOnlyModelViewSet): """Exposes list and details of `machines.models.Interface` objects. """ diff --git a/machines/admin.py b/machines/admin.py index 11da2da3..26d7a6a3 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -39,12 +39,12 @@ from .models import ( Txt, DName, Srv, + SshFp, Nas, Service, OuverturePort, Ipv6List, OuverturePortList, - SshFingerprint, ) @@ -107,6 +107,11 @@ class SrvAdmin(VersionAdmin): pass +class SshFpAdmin(VersionAdmin): + """ Admin view of a SSHFP object """ + pass + + class NasAdmin(VersionAdmin): """ Admin view of a Nas object """ pass @@ -142,10 +147,6 @@ class ServiceAdmin(VersionAdmin): list_display = ('service_type', 'min_time_regen', 'regular_time_regen') -class SshFingerprintAdmin(VersionAdmin): - """ Admin view of a SshFprAlgo object """ - pass - admin.site.register(Machine, MachineAdmin) admin.site.register(MachineType, MachineTypeAdmin) admin.site.register(IpType, IpTypeAdmin) @@ -156,6 +157,7 @@ admin.site.register(Ns, NsAdmin) admin.site.register(Txt, TxtAdmin) admin.site.register(DName, DNameAdmin) admin.site.register(Srv, SrvAdmin) +admin.site.register(SshFp, SshFpAdmin) admin.site.register(IpList, IpListAdmin) admin.site.register(Interface, InterfaceAdmin) admin.site.register(Domain, DomainAdmin) @@ -165,4 +167,3 @@ admin.site.register(Ipv6List, Ipv6ListAdmin) admin.site.register(Nas, NasAdmin) admin.site.register(OuverturePort, OuverturePortAdmin) admin.site.register(OuverturePortList, OuverturePortListAdmin) -admin.site.register(SshFingerprint, SshFingerprintAdmin) diff --git a/machines/forms.py b/machines/forms.py index 79a2e2c6..23c2aa39 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -56,11 +56,11 @@ from .models import ( Service, Vlan, Srv, + SshFp, Nas, IpType, OuverturePortList, Ipv6List, - SshFingerprint, ) @@ -598,15 +598,15 @@ class EditOuverturePortListForm(FormRevMixin, ModelForm): ) -class SshFingerprintForm(FormRevMixin, ModelForm): - """Edits a SSH fingerprint.""" +class SshFpForm(FormRevMixin, ModelForm): + """Edits a SSHFP record.""" class Meta: - model = SshFingerprint + model = SshFp exclude = ('machine',) def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(SshFingerprintForm, self).__init__( + super(SshFpForm, self).__init__( *args, prefix=prefix, **kwargs diff --git a/machines/migrations/0085_dname.py b/machines/migrations/0084_dname.py similarity index 94% rename from machines/migrations/0085_dname.py rename to machines/migrations/0084_dname.py index 86a9dc2d..4cbeb492 100644 --- a/machines/migrations/0085_dname.py +++ b/machines/migrations/0084_dname.py @@ -10,7 +10,7 @@ import re2o.mixins class Migration(migrations.Migration): dependencies = [ - ('machines', '0084_sshfingerprint'), + ('machines', '0083_remove_duplicate_rights'), ] operations = [ diff --git a/machines/migrations/0084_sshfingerprint.py b/machines/migrations/0085_sshfingerprint.py similarity index 80% rename from machines/migrations/0084_sshfingerprint.py rename to machines/migrations/0085_sshfingerprint.py index e5e8861d..47f11d07 100644 --- a/machines/migrations/0084_sshfingerprint.py +++ b/machines/migrations/0085_sshfingerprint.py @@ -10,12 +10,12 @@ import re2o.mixins class Migration(migrations.Migration): dependencies = [ - ('machines', '0083_remove_duplicate_rights'), + ('machines', '0084_dname'), ] operations = [ migrations.CreateModel( - name='SshFingerprint', + name='SshFp', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('pub_key_entry', models.TextField(help_text='SSH public key', max_length=2048)), @@ -24,9 +24,9 @@ class Migration(migrations.Migration): ('machine', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='machines.Machine')), ], options={ - 'verbose_name': 'SSH fingerprint', - 'verbose_name_plural': 'SSH fingerprints', - 'permissions': (('view_sshfingerprint', 'Can see an SSH fingerprint'),), + 'verbose_name': 'SSHFP record', + 'verbose_name_plural': 'SSHFP records', + 'permissions': (('view_sshfp', 'Can see an SSHFP record'),), }, bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model), ), diff --git a/machines/models.py b/machines/models.py index 2b85efd8..7be76e74 100644 --- a/machines/models.py +++ b/machines/models.py @@ -203,72 +203,6 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name) -class SshFingerprint(RevMixin, AclMixin, models.Model): - """A fingerpirnt of an SSH public key""" - - ALGO = ( - ("ssh-rsa", "ssh-rsa"), - ("ssh-ed25519", "ssh-ed25519"), - ("ecdsa-sha2-nistp256", "ecdsa-sha2-nistp256"), - ("ecdsa-sha2-nistp384", "ecdsa-sha2-nistp384"), - ("ecdsa-sha2-nistp521", "ecdsa-sha2-nistp521"), - ) - - machine = models.ForeignKey('Machine', on_delete=models.CASCADE) - pub_key_entry = models.TextField( - help_text="SSH public key", - max_length=2048 - ) - algo = models.CharField( - choices=ALGO, - max_length=32 - ) - comment = models.CharField( - help_text="Comment", - max_length=255, - null=True, - blank=True - ) - - @cached_property - def algo_id(self): - """Return the id of the algorithme for this key""" - if "ecdsa" in self.algo: - return 3 - elif "rsa" in self.algo: - return 1 - else: - return 2 - - @cached_property - def hash(self): - """Return the hashs for the pub key with correct id - cf RFC, 1 is sha1 , 2 sha256""" - return { - "1" : hashlib.sha1(base64.b64decode(self.pub_key_entry)).hexdigest(), - "2" : hashlib.sha256(base64.b64decode(self.pub_key_entry)).hexdigest(), - } - - class Meta: - permissions = ( - ("view_sshfingerprint", "Can see an SSH fingerprint"), - ) - verbose_name = "SSH fingerprint" - verbose_name_plural = "SSH fingerprints" - - def can_view(self, user_request, *_args, **_kwargs): - return self.machine.can_view(user_request, *_args, **_kwargs) - - def can_edit(self, user_request, *args, **kwargs): - return self.machine.can_edit(user_request, *args, **kwargs) - - def can_delete(self, user_request, *args, **kwargs): - return self.machine.can_delete(user_request, *args, **kwargs) - - def __str__(self): - return str(self.algo) + ' ' + str(self.comment) - - class MachineType(RevMixin, AclMixin, models.Model): """ Type de machine, relié à un type d'ip, affecté aux interfaces""" PRETTY_NAME = "Type de machine" @@ -631,13 +565,11 @@ class Extension(RevMixin, AclMixin, models.Model): entry += "@ IN AAAA " + str(self.origin_v6) return entry - def get_associated_sshfpr(self): + def get_associated_sshfp_records(self): from re2o.utils import all_active_assigned_interfaces return (all_active_assigned_interfaces() .filter(type__ip_type__extension=self) - .filter( - machine__id__in=SshFingerprint.objects.values('machine') - )) + .filter(machine__id__in=SshFp.objects.values('machine'))) def get_associated_a_records(self): from re2o.utils import all_active_assigned_interfaces @@ -831,6 +763,73 @@ class Srv(RevMixin, AclMixin, models.Model): str(self.port) + ' ' + str(self.target) + '.' +class SshFp(RevMixin, AclMixin, models.Model): + """A fingerprint of an SSH public key""" + + ALGO = ( + ("ssh-rsa", "ssh-rsa"), + ("ssh-ed25519", "ssh-ed25519"), + ("ecdsa-sha2-nistp256", "ecdsa-sha2-nistp256"), + ("ecdsa-sha2-nistp384", "ecdsa-sha2-nistp384"), + ("ecdsa-sha2-nistp521", "ecdsa-sha2-nistp521"), + ) + + machine = models.ForeignKey('Machine', on_delete=models.CASCADE) + pub_key_entry = models.TextField( + help_text="SSH public key", + max_length=2048 + ) + algo = models.CharField( + choices=ALGO, + max_length=32 + ) + comment = models.CharField( + help_text="Comment", + max_length=255, + null=True, + blank=True + ) + + @cached_property + def algo_id(self): + """Return the id of the algorithm for this key""" + if "ecdsa" in self.algo: + return 3 + elif "rsa" in self.algo: + return 1 + else: + return 2 + + @cached_property + def hash(self): + """Return the hashess for the pub key with correct id + cf RFC, 1 is sha1 , 2 sha256""" + return { + "1" : hashlib.sha1(base64.b64decode(self.pub_key_entry)).hexdigest(), + "2" : hashlib.sha256(base64.b64decode(self.pub_key_entry)).hexdigest(), + } + + class Meta: + permissions = ( + ("view_sshfp", "Can see an SSHFP record"), + ) + verbose_name = "SSHFP record" + verbose_name_plural = "SSHFP records" + + def can_view(self, user_request, *_args, **_kwargs): + return self.machine.can_view(user_request, *_args, **_kwargs) + + def can_edit(self, user_request, *args, **kwargs): + return self.machine.can_edit(user_request, *args, **kwargs) + + def can_delete(self, user_request, *args, **kwargs): + return self.machine.can_delete(user_request, *args, **kwargs) + + def __str__(self): + return str(self.algo) + ' ' + str(self.comment) + + + class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): """ Une interface. Objet clef de l'application machine : - une address mac unique. Possibilité de la rendre unique avec le diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index e5599858..ba736f10 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -119,9 +119,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} - {% can_create SshFingerprint interface.machine.id %} + {% can_create SshFp interface.machine.id %}