diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 5ec97d042..62460146e 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -1,8 +1,9 @@ # coding:utf-8 from django import forms +from django.utils.translation import gettext_lazy as _ from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser, Tag -from django.utils.translation import gettext_lazy as _ +from common.utils import validate_ssh_private_key, ssh_pubkey_gen # class AssetForm(forms.ModelForm): @@ -141,7 +142,6 @@ class AdminUserForm(forms.ModelForm): widget=forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select assets')}) ) - auto_generate_key = forms.BooleanField(required=True, initial=True) # Form field name can not start with `_`, so redefine it, password = forms.CharField(widget=forms.PasswordInput, max_length=100, min_length=8, strip=True, help_text=_('If also set private key, use that first'), required=False) @@ -166,21 +166,36 @@ class AdminUserForm(forms.ModelForm): # Because we define custom field, so we need rewrite :method: `save` admin_user = super(AdminUserForm, self).save(commit=commit) password = self.cleaned_data['password'] - private_key_file = self.cleaned_data['private_key_file'] + private_key = self.cleaned_data['private_key_file'] + public_key = ssh_pubkey_gen(private_key) if password: admin_user.password = password - print(password) - # Todo: Validate private key file, and generate public key - # Todo: Auto generate private key and public key - if private_key_file: - admin_user.private_key = private_key_file.read() + if private_key: + admin_user.private_key = private_key + admin_user.public_key = public_key admin_user.save() - return self.instance + return admin_user + + def clean_private_key_file(self): + private_key_file = self.cleaned_data['private_key_file'] + if private_key_file: + private_key = private_key_file.read() + if not validate_ssh_private_key(private_key): + raise forms.ValidationError(_('Invalid private key')) + return private_key + return private_key_file + + def clean(self): + password = self.cleaned_data['password'] + private_key_file = self.cleaned_data.get('private_key_file', '') + + if not (password or private_key_file): + raise forms.ValidationError(_('Password and private key file must be input one')) class Meta: model = AdminUser - fields = ['name', 'username', 'auto_generate_key', 'password', 'private_key_file', 'as_default', 'comment'] + fields = ['name', 'username', 'password', 'private_key_file', 'comment'] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), diff --git a/apps/assets/models.py b/apps/assets/models.py index c07833982..2ac4aabe8 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -6,8 +6,9 @@ from django.db import models from django.core import serializers import logging from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError -from common.utils import signer +from common.utils import signer, validate_ssh_private_key logger = logging.getLogger(__name__) @@ -91,13 +92,21 @@ class AssetExtend(models.Model): unique_together = ('key', 'value') +def private_key_validator(value): + if not validate_ssh_private_key(value): + raise ValidationError( + _('%(value)s is not an even number'), + params={'value': value}, + ) + + class AdminUser(models.Model): name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) username = models.CharField(max_length=16, verbose_name=_('Username')) - _password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) - _private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key')) + _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + _private_key = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), + validators=[private_key_validator,]) _public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key')) - as_default = models.BooleanField(default=False, verbose_name=_('As default')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, null=True) created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by')) diff --git a/apps/assets/templates/assets/admin_user_create_update.html b/apps/assets/templates/assets/admin_user_create_update.html index 7738adec0..575ab667a 100644 --- a/apps/assets/templates/assets/admin_user_create_update.html +++ b/apps/assets/templates/assets/admin_user_create_update.html @@ -31,20 +31,8 @@ {% csrf_token %} {{ form.name|bootstrap_horizontal }} {{ form.username|bootstrap_horizontal }} -
- -
- {{ form.auto_generate_key}} -
-
{{ form.password|bootstrap_horizontal }} {{ form.private_key_file|bootstrap_horizontal }} -
- -
- {{ form.as_default}} -
-
{{ form.assets|bootstrap_horizontal }} {{ form.comment|bootstrap_horizontal }} diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index bc5d01692..0451d989f 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -1,22 +1,10 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block custom_head_css_js %} -{{ block.super }} - +{% block table_search %} {% endblock %} -{% block table_search %}{% endblock %} {% block table_container %}
- {% trans "Create IDC" %} + {% trans "Create admin user" %}
@@ -29,7 +17,7 @@ - + @@ -59,9 +47,10 @@ $(document).ready(function(){ {# }#} {# }},#} {targets: 6, createdCell: function (td, cellData, rowData) { - var update_btn = '{% trans "Update" %}'.replace('99991937', cellData); + var script_btn = '{% trans "Script" %}'.replace('99991937', cellData); + var update_btn = '{% trans "Update" %}'.replace('99991937', cellData); var del_btn = '{% trans "Delete" %}'.replace('99991937', cellData); - $(td).html(update_btn + del_btn) + $(td).html(script_btn + update_btn + del_btn) }}], ajax_url: '{% url "assets:api-admin-user-list" %}', columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () {return 'lost'} }, diff --git a/apps/assets/views.py b/apps/assets/views.py index 8f5803552..612379602 100644 --- a/apps/assets/views.py +++ b/apps/assets/views.py @@ -483,6 +483,9 @@ class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie )) return success_message + def form_invalid(self, form): + return super(AdminUserCreateView, self).form_invalid(form) + class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView): model = AdminUser diff --git a/apps/common/utils.py b/apps/common/utils.py index d3bd61ddf..9a998adf9 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -3,11 +3,14 @@ from __future__ import unicode_literals from six import string_types +import os from itertools import chain import string import logging import datetime +import paramiko +import paramiko from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \ BadSignature, SignatureExpired from django.shortcuts import reverse as dj_reverse @@ -15,6 +18,11 @@ from django.conf import settings from django.core import signing from django.utils import timezone +try: + import cStringIO as StringIO +except ImportError: + import StringIO + SECRET_KEY = settings.SECRET_KEY @@ -162,4 +170,70 @@ def timesince(dt, since='', default="just now"): return default +def ssh_key_string_to_obj(text): + key_f = StringIO.StringIO(text) + key = None + try: + key = paramiko.RSAKey.from_private_key(key_f) + except paramiko.SSHException: + pass + + try: + key = paramiko.DSSKey.from_private_key(key_f) + except paramiko.SSHException: + pass + return key + + +def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'): + if isinstance(private_key, string_types): + private_key = ssh_key_string_to_obj(private_key) + + if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)): + raise IOError('Invalid private key') + + public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % { + 'key_type': private_key.get_name(), + 'key_content': private_key.get_base64(), + 'username': username, + 'hostname': hostname, + } + return public_key + + +def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None): + """Generate user ssh private and public key + + Use paramiko RSAKey generate it. + :return private key str and public key str + """ + + if hostname is None: + hostname = os.uname()[1] + + f = StringIO.StringIO() + + try: + if type == 'rsa': + private_key_obj = paramiko.RSAKey.generate(length) + elif type == 'dsa': + private_key_obj = paramiko.DSSKey.generate(length) + else: + raise IOError('SSH private key must be `rsa` or `dsa`') + private_key_obj.write_private_key(f, password=password) + private_key = f.getvalue() + public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname) + return private_key, public_key + except IOError: + raise IOError('These is error when generate ssh key.') + + +def validate_ssh_private_key(text): + key = ssh_key_string_to_obj(text) + if key is None: + return False + else: + return True + + signer = Signer() \ No newline at end of file diff --git a/apps/users/utils.py b/apps/users/utils.py index 5291584ae..129bc6d78 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -34,36 +34,7 @@ class AdminUserRequiredMixin(UserPassesTestMixin): return self.request.user.is_staff -def ssh_key_gen(length=2048, password=None, username='root', hostname=None): - """Generate user ssh private and public key - Use paramiko RSAKey generate it. - - """ - - if hostname is None: - hostname = os.uname()[1] - - f = StringIO.StringIO() - - try: - logger.debug(_('Begin to generate ssh private key ...')) - private_key_obj = RSAKey.generate(length) - private_key_obj.write_private_key(f, password=password) - private_key = f.getvalue() - - public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % { - 'key_type': private_key_obj.get_name(), - 'key_content': private_key_obj.get_base64(), - 'username': username, - 'hostname': hostname, - } - - logger.debug(_('Finish to generate ssh private key ...')) - return private_key, public_key - - except IOError: - raise IOError(_('These is error when generate ssh key.')) def user_add_success_next(user): diff --git a/requirements.txt b/requirements.txt index 4b3926c50..f4fcdac3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ django-simple-captcha==0.5.2 django-formtools==1.0 sshpubkeys==2.2.0 djangorestframework-bulk==0.2.1 +paramiko==2.0.2
{% trans 'Asset num' %} {% trans 'Lost connection' %} {% trans 'Comment' %}{% trans 'Action' %}