diff --git a/apps/cmdb/__init__.py b/apps/cmdb/__init__.py index e69de29..4490fa6 100644 --- a/apps/cmdb/__init__.py +++ b/apps/cmdb/__init__.py @@ -0,0 +1 @@ +default_app_config = 'cmdb.apps.CmdbConfig' \ No newline at end of file diff --git a/apps/cmdb/apps.py b/apps/cmdb/apps.py index f555ae7..a9135fa 100644 --- a/apps/cmdb/apps.py +++ b/apps/cmdb/apps.py @@ -2,4 +2,7 @@ from django.apps import AppConfig class CmdbConfig(AppConfig): - name = 'cmdb' \ No newline at end of file + name = 'cmdb' + + def ready(self): + from .signals import auto_delete_file \ No newline at end of file diff --git a/apps/cmdb/models.py b/apps/cmdb/models.py index 6927c8d..1151a50 100644 --- a/apps/cmdb/models.py +++ b/apps/cmdb/models.py @@ -1,9 +1,9 @@ -import os from datetime import datetime from django.db import models from django.contrib.auth import get_user_model -from django.dispatch import receiver + +from simple_history.models import HistoricalRecords User = get_user_model() @@ -99,20 +99,24 @@ class DeviceInfo(AbstractMode, DeviceAbstract, TimeAbstract): buyDate = models.DateField(default=datetime.now, verbose_name="购买日期") warrantyDate = models.DateField(default=datetime.now, verbose_name="到保日期") desc = models.TextField(blank=True, default='', verbose_name='备注信息') + changed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) + history = HistoricalRecords(excluded_fields=['add_time', 'modify_time']) class Meta: verbose_name = '设备信息' verbose_name_plural = verbose_name + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + self.changed_by = value + class DeviceFile(TimeAbstract): device = models.ForeignKey('DeviceInfo', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='设备') file_content = models.FileField(upload_to="asset_file/%Y/%m", null=True, blank=True, verbose_name="资产文件") upload_user = models.CharField(max_length=20, verbose_name="上传人") - -@receiver(models.signals.post_delete, sender=DeviceFile) -def auto_delete_file(sender, instance, **kwargs): - if instance.file_content: - if os.path.isfile(instance.file_content.path): - os.remove(instance.file_content.path) diff --git a/apps/cmdb/signals.py b/apps/cmdb/signals.py new file mode 100644 index 0000000..d66e53a --- /dev/null +++ b/apps/cmdb/signals.py @@ -0,0 +1,13 @@ +import os + +from django.dispatch import receiver +from django.db.models.signals import post_delete + +from .models import DeviceFile + + +@receiver(post_delete, sender=DeviceFile) +def auto_delete_file(sender, instance, **kwargs): + if instance.file_content: + if os.path.isfile(instance.file_content.path): + os.remove(instance.file_content.path) \ No newline at end of file diff --git a/apps/utils/sandbox_utils.py b/apps/utils/sandbox_utils.py index 44c3e4b..1afcf92 100644 --- a/apps/utils/sandbox_utils.py +++ b/apps/utils/sandbox_utils.py @@ -8,6 +8,8 @@ from django.conf import settings import yaml import logging +import nmap +import paramiko os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sandboxMP.settings') error_logger = logging.getLogger('sandbox_error') @@ -72,3 +74,136 @@ class ConfigFileMixin: """ key = ['hosts', 'net_address'] return self.get_conf_content(*key) + + +class SandboxScan(ConfigFileMixin): + + def basic_scan(self): + """ + Use ICMP discovery online hosts and return online hosts. + """ + hosts = self.get_net_address() + nm = nmap.PortScanner() + nm.scan(hosts=hosts, arguments='-n -sP -PE') + online_hosts = nm.all_hosts() + return online_hosts + + def os_scan(self): + """ + Get the system type by nmap scan and return hosts list with os type. + """ + hosts = self.get_net_address() + nm = nmap.PortScanner() + nm.scan(hosts=hosts, arguments='-n sS -O') + online_hosts = [] + for host in nm.all_hosts(): + try: + os_type = nm[host]['osmatch'][0]['osclass'][0]['osfamily'] + except Exception: + os_type = 'unknown' + host_dict = {'host': host, 'os': os_type} + online_hosts.append(host_dict) + return online_hosts + + def get_net_address(self): + """ + Return the hosts that will be used to scan. + Subclasses can override this to return any hosts.` + """ + hosts_list = super().get_net_address() + hosts = ' '.join(str(i) for i in hosts_list) + return hosts + + +class LoginExecution(ConfigFileMixin): + + def login_execution(self, auth_type='password', **kwargs): + """ + Support two authentication modes: password or private_key, and auth_type default is password. + """ + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + if auth_type == 'password': + ssh.connect( + kwargs['hostname'], + kwargs['port'], + kwargs['username'], + kwargs['password'], + timeout=3, + ) + kwargs['auth_type'] = 'password' + elif auth_type == 'private_key': + kwargs['auth_type'] = 'private_key' + private_key = paramiko.RSAKey.from_private_key_file(kwargs['private_key']) + ssh.connect( + kwargs['hostname'], + kwargs['port'], + kwargs['username'], + private_key, + timeout=3, + ) + kwargs['status'] = 'succeed' + kwargs['error_message'] = '' + commands = self.get_commands() + for key, value in commands.items(): + stdin, stdout, stderr = ssh.exec_command(value, timeout=5) + result = str(stdout.read()).strip('b').strip("'").strip('\\n') + kwargs[key] = result + except Exception as e: + msg = '%(exc)s hostname %(hostname)s' % { + 'exc': e, + 'hostname': kwargs['hostname'] + } + error_logger.error(msg) + kwargs['status'] = 'failed' + kwargs['error_message'] = str(e) + return kwargs + + def password_login_execution(self, **kwargs): + """ + Login to the remote system with a password. + Kwargs is a dict containing hostname, port, username and password. + Example: kwargs = {'hostname': '172.16.3.101', 'port': 22, 'username': 'root', 'password': 'paw123'} + """ + return self.login_execution(**kwargs) + + def private_key_login_execution(self, **kwargs): + """ + Login to the remote system with a private_key. + Kwargs is a dict containing hostname, port, username and private key. + Example:kwargs = {'hostname': '172.16.3.101', 'port': 22, 'username': 'root', 'private_key': '/root/.ssh/id_rsa'} + """ + return self.login_execution(auth_type='private_key', **kwargs) + + def get_auth_type(self): + key = ['hosts', 'auth_type'] + return self.get_conf_content(*key) + + def get_ssh_username(self): + key = ['hosts', 'ssh_username'] + return self.get_conf_content(*key) + + def get_ssh_port(self): + key = ['hosts', 'ssh_port'] + return self.get_conf_content(*key) + + def get_ssh_password(self): + key = ['hosts', 'ssh_password'] + return self.get_conf_content(*key) + + def get_ssh_private_key(self): + key = ['hosts', 'ssh_private_key'] + return self.get_conf_content(*key) + + def get_email(self): + key = ['hosts', 'email'] + return self.get_conf_content(*key) + + def get_send_email(self): + key = ['hosts', 'send_email'] + return self.get_conf_content(*key) + + def get_scan_type(self): + key = ['hosts', 'scan_type'] + return self.get_conf_content(*key) \ No newline at end of file diff --git a/requirements/pro.txt b/requirements/pro.txt index fff54da..bc887e7 100644 --- a/requirements/pro.txt +++ b/requirements/pro.txt @@ -8,7 +8,7 @@ python-nmap==0.6.1 redis==3.0.1 pymongo==3.7.1 paramiko==2.4.2 -pycrypto==2.6.1 +django-simple-history==2.6.0 celery==4.2.1 celery-once==2.0.0 flower==0.9.2 diff --git a/sandboxMP/settings.py b/sandboxMP/settings.py index 8eecb8b..c97f46c 100644 --- a/sandboxMP/settings.py +++ b/sandboxMP/settings.py @@ -39,6 +39,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'simple_history', 'system', 'cmdb', ] @@ -53,6 +54,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'apps.system.middleware.MenuCollection', 'apps.system.middleware.RbacMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'sandboxMP.urls'