diff --git a/apps/assets/api.py b/apps/assets/api.py index 860f7111c..1fe371dcd 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -44,9 +44,11 @@ class AssetViewSet(IDInFilterMixin, BulkModelViewSet): else: assets_granted = get_user_granted_assets(self.request.user) queryset = self.queryset.filter(id__in=[asset.id for asset in assets_granted]) + cluster_id = self.request.query_params.get('cluster_id') asset_group_id = self.request.query_params.get('asset_group_id') admin_user_id = self.request.query_params.get('admin_user_id') + if cluster_id: queryset = queryset.filter(cluster__id=cluster_id) if asset_group_id: diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 2bf6df507..e41044f16 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -1,5 +1,4 @@ # coding:utf-8 -import uuid from django import forms from django.utils.translation import gettext_lazy as _ @@ -9,7 +8,6 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, logger = get_logger(__file__) -from rest_framework import serializers class AssetCreateForm(forms.ModelForm): @@ -57,11 +55,11 @@ class AssetUpdateForm(forms.ModelForm): class AssetBulkUpdateForm(forms.ModelForm): - assets = forms.MultipleChoiceField( + assets = forms.ModelMultipleChoiceField( required=True, help_text='* required', label=_('Select assets'), - choices=[(asset.id, asset.hostname) for asset in Asset.objects.all()], + queryset=Asset.objects.all(), widget=forms.SelectMultiple( attrs={ 'class': 'select2', @@ -94,10 +92,9 @@ class AssetBulkUpdateForm(forms.ModelForm): cleaned_data = {k: v for k, v in self.cleaned_data.items() if k in changed_fields} - print(cleaned_data) - assets_id = cleaned_data.pop('assets') + assets = cleaned_data.pop('assets') groups = cleaned_data.pop('groups', []) - assets = Asset.objects.filter(id__in=assets_id) + assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) assets.update(**cleaned_data) if groups: for asset in assets: @@ -175,16 +172,18 @@ class AdminUserForm(forms.ModelForm): password = None if private_key: - public_key = ssh_pubkey_gen(private_key) + public_key = ssh_pubkey_gen(private_key, password=password) admin_user.set_auth(password=password, public_key=public_key, private_key=private_key) return admin_user def clean_private_key_file(self): private_key_file = self.cleaned_data['private_key_file'] + password = self.cleaned_data['password'] + if private_key_file: private_key = private_key_file.read() - if not validate_ssh_private_key(private_key): + if not validate_ssh_private_key(private_key, password): raise forms.ValidationError(_('Invalid private key')) return private_key return private_key_file diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 1bbc2ddd7..02d2724d6 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -18,10 +18,6 @@ __all__ = ['Asset'] logger = logging.getLogger(__name__) -def get_default_cluster(): - return Cluster.initial() - - class Asset(models.Model): # Todo: Move them to settings STATUS_CHOICES = ( @@ -48,7 +44,7 @@ class Asset(models.Model): hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) port = models.IntegerField(default=22, verbose_name=_('Port')) groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) - cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster'),) + cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),) diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py index 965ceb1f3..18b0c5289 100644 --- a/apps/assets/models/group.py +++ b/apps/assets/models/group.py @@ -10,7 +10,6 @@ from django.db import models import logging from django.utils.translation import ugettext_lazy as _ -from .user import SystemUser __all__ = ['AssetGroup'] logger = logging.getLogger(__name__) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 56651b4eb..674ffa008 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -66,16 +66,15 @@ class AssetUser(models.Model): @property def private_key_file(self): - if not self.private_key: + if not self.private_key_obj: return None project_dir = settings.PROJECT_DIR tmp_dir = os.path.join(project_dir, 'tmp') key_str = signer.unsign(self._private_key) - key_name = md5(key_str.encode('utf-8')).hexdigest() + key_name = '.' + md5(key_str.encode('utf-8')).hexdigest() key_path = os.path.join(tmp_dir, key_name) if not os.path.exists(key_path): - with open(key_path, 'w') as f: - f.write(key_str) + self.private_key_obj.write_private_key_file(key_path) os.chmod(key_path, 0o400) return key_path @@ -105,7 +104,6 @@ class AssetUser(models.Model): update_fields.append('_public_key') if update_fields: - print(update_fields) self.save(update_fields=update_fields) def auto_gen_auth(self): @@ -149,7 +147,11 @@ class AdminUser(AssetUser): @property def become_pass(self): - return signer.unsign(self._become_pass) + password = signer.unsign(self._become_pass) + if password: + return password + else: + return "" @become_pass.setter def become_pass(self, password): @@ -199,7 +201,7 @@ class SystemUser(AssetUser): ('K', 'Public key'), ) cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster")) - priority = models.IntegerField(default=10, verbose_name=_("Priority")) # Todo: If user granted more priority user, default will be login as the hign + priority = models.IntegerField(default=10, verbose_name=_("Priority")) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo')) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 09430da96..a46e697fc 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -402,12 +402,6 @@ def push_system_user_on_auth_change(sender, instance=None, update_fields=None, * push_system_user_to_cluster_assets.delay(instance, task_name) -@receiver(on_app_ready, dispatch_uid="my_unique_identifier") -def test_admin_user_on_app_ready(sender, **kwargs): - logger.debug("Receive app ready signal, test admin connectability") - test_admin_user_connectability_period.delay() - - celery_app.conf['CELERYBEAT_SCHEDULE'].update( { 'update_assets_hardware_period': { diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index dce243c0e..cb5b082cc 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -63,7 +63,7 @@
{% trans 'Type' %} | {% trans 'Env' %} | {% trans 'Hardware' %} | -{% trans 'Valid' %} | -{% trans 'Alive' %} | +{% trans 'Active' %} | +{% trans 'Connective' %} | @@ -48,216 +44,45 @@ {% block custom_foot_js %} diff --git a/apps/assets/utils.py b/apps/assets/utils.py index e21a2120e..ab63bbafc 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,5 +1,6 @@ # ~*~ coding: utf-8 ~*~ # +from collections import defaultdict from common.utils import get_object_or_none from .models import Asset, SystemUser @@ -17,3 +18,12 @@ def get_system_user_by_name(name): return system_user +def check_assets_have_system_user(assets, system_users): + errors = defaultdict(list) + + for system_user in system_users: + clusters = system_user.cluster.all() + for asset in assets: + if asset.cluster not in clusters: + errors[asset].append(system_user) + return errors diff --git a/apps/common/models.py b/apps/common/models.py index bd4b2abe9..beeb30826 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -1,5 +1,2 @@ -from __future__ import unicode_literals - from django.db import models -# Create your models here. diff --git a/apps/common/utils.py b/apps/common/utils.py index 236ae2e02..e7b07860b 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -195,11 +195,11 @@ def ssh_key_string_to_obj(text, password=None): return key -def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'): +def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None): if isinstance(private_key, bytes): private_key = private_key.decode("utf-8") if isinstance(private_key, string_types): - private_key = ssh_key_string_to_obj(private_key) + private_key = ssh_key_string_to_obj(private_key, password=password) if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)): raise IOError('Invalid private key') @@ -238,14 +238,14 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h raise IOError('These is error when generate ssh key.') -def validate_ssh_private_key(text): +def validate_ssh_private_key(text, password=None): if isinstance(text, bytes): try: text = text.decode("utf-8") except UnicodeDecodeError: return False - key = ssh_key_string_to_obj(text) + key = ssh_key_string_to_obj(text, password=password) if key is None: return False else: diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 783c29710..e8b2d19df 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -59,7 +59,6 @@ INSTALLED_APPS = [ 'assets.apps.AssetsConfig', 'perms.apps.PermsConfig', 'ops.apps.OpsConfig', - # 'audits.apps.AuditsConfig', 'common.apps.CommonConfig', 'terminal.apps.TerminalConfig', 'rest_framework', diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 37df92beb..810b14c51 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -1,7 +1,5 @@ # ~*~ coding: utf-8 ~*~ -from collections import defaultdict - from ansible.plugins.callback import CallbackBase from ansible.plugins.callback.default import CallbackModule @@ -21,9 +19,8 @@ class AdHocResultCallback(CallbackModule): # "contacted": {"hostname",...}, # "dark": {"hostname": {"task_name": {}, "task_name": {}},...,}, # } - self.results_raw = dict(ok=defaultdict(dict), failed=defaultdict(dict), - unreachable=defaultdict(dict), skipped=defaultdict(dict)) - self.results_summary = dict(contacted=[], dark=defaultdict(dict)) + self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={}) + self.results_summary = dict(contacted=[], dark={}) super().__init__() def gather_result(self, t, res): @@ -34,8 +31,8 @@ class AdHocResultCallback(CallbackModule): if self.results_raw[t].get(host): self.results_raw[t][host][task_name] = task_result - # else: - # self.results_raw[t][host] = {task_name: task_result} + else: + self.results_raw[t][host] = {task_name: task_result} self.clean_result(t, host, task_name, task_result) def clean_result(self, t, host, task_name, task_result): @@ -45,10 +42,10 @@ class AdHocResultCallback(CallbackModule): if host not in contacted: contacted.append(host) else: - # if dark.get(host): - dark[host][task_name] = task_result.values - # else: - # dark[host] = {task_name: task_result} + if dark.get(host): + dark[host][task_name] = task_result.values + else: + dark[host] = {task_name: task_result} if host in contacted: contacted.remove(host) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index d3d342368..94dfaa984 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -32,6 +32,7 @@ class BaseHost(Host): } """ self.host_data = host_data + print(host_data) hostname = host_data.get('hostname') or host_data.get('ip') port = host_data.get('port') or 22 super().__init__(hostname, port) diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index c08fad156..503f1046a 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -27,7 +27,7 @@{% trans 'Name' %} | -{% trans 'F/S/T' %} | +{% trans 'Run times' %} | {% trans 'Versions' %} | {% trans 'Hosts' %} | {% trans 'Success' %} | diff --git a/apps/ops/utils.py b/apps/ops/utils.py index a79e10aa7..994e81db0 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -1,8 +1,8 @@ # ~*~ coding: utf-8 ~*~ -import re import time from django.utils import timezone +from django.db import transaction from common.utils import get_logger, get_object_or_none, get_short_uuid_str from .ansible import AdHocRunner, CommandResultCallback @@ -58,6 +58,7 @@ def get_inventory(hostname_list, run_as_admin=False, run_as=None, become_info=No ) +@record_adhoc def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info=None): inventory = get_inventory( hostname_list, run_as_admin=run_as_admin, @@ -67,7 +68,6 @@ def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info return runner -@record_adhoc def run_adhoc_object(adhoc, **options): """ :param adhoc: Instance of AdHoc @@ -109,6 +109,8 @@ def create_or_update_task( run_as_admin=False, run_as="", become_info=None, created_by=None ): + print(options) + print(task_name) task = get_object_or_none(Task, name=task_name) if task is None: task = Task(name=task_name, created_by=created_by) @@ -125,6 +127,7 @@ def create_or_update_task( if not adhoc or adhoc != new_adhoc: new_adhoc.save() task.latest_adhoc = new_adhoc + print("Return task") return task diff --git a/apps/perms/forms.py b/apps/perms/forms.py index f6f41f624..c4c4ff0c9 100644 --- a/apps/perms/forms.py +++ b/apps/perms/forms.py @@ -38,3 +38,34 @@ class AssetPermissionForm(forms.ModelForm): 'user_groups': _('User or user group at least one required'), 'asset_groups': _('Asset or Asset group at least one required'), } + + def clean_system_users(self): + from assets.utils import check_assets_have_system_user + + errors = [] + assets = self.cleaned_data['assets'] + asset_groups = self.cleaned_data['asset_groups'] + system_users = self.cleaned_data['system_users'] + + error_data = check_assets_have_system_user(assets, system_users) + if error_data: + for asset, system_users in error_data.items(): + msg = _("Asset {} not have [{}] system users, please check \n") + error = forms.ValidationError(msg.format( + asset.hostname, + ", ".join(system_user.name for system_user in system_users) + )) + errors.append(error) + + for group in asset_groups: + msg = _("Asset {}: {} not have [{}] system users, please check") + assets = group.assets.all() + error_data = check_assets_have_system_user(assets, system_users) + for asset, system_users in error_data.items(): + errors.append(msg.format( + group.name, asset.hostname, + ", ".join(system_user.name for system_user in system_users) + )) + if errors: + raise forms.ValidationError(errors) + return self.cleaned_data['system_users'] diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index b8c2a60f0..86a11ba62 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -55,7 +55,7 @@
---|