jumpserver/apps/ops/models.py

425 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals, absolute_import
import logging
import json
from jinja2 import Template
from django.db import models
from assets.models import Asset
from django.utils.translation import ugettext_lazy as _
logger = logging.getLogger(__name__)
class Tasker(models.Model):
uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start Time'))
end = models.DateTimeField(blank=True, null=True, verbose_name=_('End Time'))
exit_code = models.IntegerField(default=0, verbose_name=_('Exit Code'))
completed = models.BooleanField(default=False, verbose_name=_('Is Completed'))
hosts = models.TextField(blank=True, null=True, verbose_name=_('Hosts'))
def __unicode__(self):
return "%s" % self.uuid
@property
def total_hosts(self):
return self.hosts.split(',')
class AnsiblePlay(models.Model):
tasker = models.ForeignKey(Tasker, related_name='plays', blank=True, null=True)
uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
def __unicode__(self):
return "%s<%s>" % (self.name, self.uuid)
def to_dict(self):
return {"uuid": self.uuid, "name": self.name}
class AnsibleTask(models.Model):
play = models.ForeignKey(AnsiblePlay, related_name='tasks', blank=True, null=True)
uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
def __unicode__(self):
return "%s<%s>" % (self.name, self.uuid)
def to_dict(self):
return {"uuid": self.uuid, "name": self.name}
def failed(self):
pass
def success(self):
pass
class AnsibleHostResult(models.Model):
task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True, null=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
success = models.TextField(blank=True, verbose_name=_('Success'))
skipped = models.TextField(blank=True, verbose_name=_('Skipped'))
failed = models.TextField(blank=True, verbose_name=_('Failed'))
unreachable = models.TextField(blank=True, verbose_name=_('Unreachable'))
no_host = models.TextField(blank=True, verbose_name=_('NoHost'))
def __unicode__(self):
return "%s %s<%s>" % (self.name, str(self.is_success), self.task.uuid)
@property
def is_failed(self):
if self.failed or self.unreachable or self.no_host:
return True
return False
@property
def is_success(self):
return not self.is_failed
@property
def _success_data(self):
if self.success:
return json.loads(self.success)
elif self.skipped:
return json.loads(self.skipped)
@property
def _failed_data(self):
if self.failed:
return json.loads(self.failed)
elif self.unreachable:
return json.loads(self.unreachable)
elif self.no_host:
return {"msg": self.no_host}
@property
def failed_msg(self):
return self._failed_data.get("msg")
@staticmethod
def __filter_disk(ansible_devices, exclude_devices):
"""
过滤磁盘设备,丢弃掉不需要的设备
:param ansible_devices: 对应的facts字段
:param exclude_devices: <list> 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃sr0子类的 ['sr']
:return: <dict> 过滤获取的结果
"""
for start_str in exclude_devices:
for key in ansible_devices.keys():
if key.startswith(start_str):
ansible_devices.pop(key)
return ansible_devices
@staticmethod
def __filter_interface(ansible_interfaces, exclude_interface):
"""
过滤网卡设备,丢弃掉不需要的网卡, 比如lo
:param ansible_interface: 对应的facts字段
:param exclude_interface: <list> 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃lo子类的 ['lo']
:return: <dict> 过滤获取的结果
"""
for interface in ansible_interfaces:
for start_str in exclude_interface:
if interface.startswith(start_str):
i = ansible_interfaces.index(interface)
ansible_interfaces.pop(i)
return ansible_interfaces
@staticmethod
def __gather_interface(facts, interfaces):
"""
收集所有interface的具体信息
:param facts: ansible faces
:param interfaces: 需要收集的intreface列表
:return: <dict> interface的详情
"""
result = {}
for key in interfaces:
gather_key = "ansible_" + key
if gather_key in facts.keys():
result[key] = facts.get(gather_key)
return result
def __deal_setup(self):
"""
处理ansible setup模块收集到的数据提取资产需要的部分
:return: <dict> {"msg": <str>, "data": <dict>}, 注意msg是异常信息, 有msg时 data为None
"""
result = self._success_data
module_name = result['invocation'].get('module_name') if result.get('invocation') else None
if module_name is not None:
if module_name != "setup":
return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None}
else:
data = {}
facts =result.get('ansible_facts')
interfaces = self.__filter_interface(facts.get('ansible_interfaces'), ['lo'])
cpu_describe = "%s %s" % (facts.get('ansible_processor')[0], facts.get('ansible_processor')[1]) if len(facts.get('ansible_processor')) >= 2 else ""
data['sn'] = facts.get('ansible_product_serial')
data['env'] = facts.get('ansible_env')
data['os'] = "%s %s(%s)" % (facts.get('ansible_distribution'),
facts.get('ansible_distribution_version'),
facts.get('ansible_distribution_release'))
data['mem'] = facts.get('ansible_memtotal_mb')
data['cpu'] = "%s %d" % (cpu_describe, facts.get('ansible_processor_count'))
data['disk'] = self.__filter_disk(facts.get('ansible_devices'), ['sr'])
data['interface'] = self.__gather_interface(facts, interfaces)
return {"msg": None, "data": data}
else:
return {"msg": "there result isn't ansible setup module result! can't process this data format", "data": None}
@property
def deal_setup(self):
try:
return self.__deal_setup()
except Exception as e:
return {"msg": "deal with setup data failed, %s" % e.message, "data": None}
def __deal_ping(self):
"""
处理ansible ping模块收集到的数据
:return: <dict> {"msg": <str>, "data": {"success": <bool>}}, 注意msg是异常信息, 有msg时 data为None
"""
result = self._success_data
module_name = result['invocation'].get('module_name') if result.get('invocation') else None
if module_name is not None:
if module_name != "ping":
return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None}
else:
ping = True if result.get('ping') == "pong" else False
return {"msg": None, "data": {"success": ping}}
else:
return {"msg": "there isn't module_name field! can't process this data format", "data": None}
@property
def deal_ping(self):
try:
return self.__deal_ping()
except Exception as e:
return {"msg": "deal with ping data failed, %s" % e.message, "data": None}
class HostAlia(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias'))
host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items'))
def __unicode__(self):
return self.name
class UserAlia(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('User_Alias'))
user_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items'))
def __unicode__(self):
return self.name
class CmdAlia(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Command_Alias'))
cmd_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items'))
def __unicode__(self):
return self.name
class RunasAlia(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias'))
runas_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items'))
def __unicode__(self):
return self.name
class Privilege(models.Model):
user = models.ForeignKey(UserAlia, blank=True, null=True, related_name='privileges')
host = models.ForeignKey(HostAlia, blank=True, null=True, related_name='privileges')
runas = models.ForeignKey(RunasAlia, blank=True, null=True, related_name='privileges')
command = models.ForeignKey(CmdAlia, blank=True, null=True, related_name='privileges')
nopassword = models.BooleanField(default=True, verbose_name=_('Is_NoPassword'))
def __unicode__(self):
return "[%s %s %s %s %s]" % (self.user.name,
self.host.name,
self.runas.name,
self.command.name,
self.nopassword)
def to_tuple(self):
return self.user.name, self.host.name, self.runas.name, self.command.name, self.nopassword
class Extra_conf(models.Model):
line = models.TextField(blank=True, null=True, verbose_name=_('Extra_Item'))
def __unicode__(self):
return self.line
class Sudo(models.Model):
"""
Sudo配置文件对象, 用于配置sudo的配置文件
:param user_alias: <dict> {<alia>: <users_list>}
:param cmnd_alias: <dict> {<alia>: <commands_list>}
:param host_alias: <dict> {<alia>: <hosts_list>}
:param runas_alias: <dict> {<alia>: <runas_list>}
:param extra_lines: <list> [<line1>, <line2>,...]
:param privileges: <list> [(user, host, runas, command, nopassword),]
"""
asset = models.ForeignKey(Asset, null=True, blank=True, related_name='sudos')
host_alias = models.ManyToManyField(HostAlia, related_name='sudos', blank=True)
user_alias = models.ManyToManyField(UserAlia, related_name='sudos', blank=True)
cmnd_alias = models.ManyToManyField(CmdAlia, related_name='sudos', blank=True)
runas_alias = models.ManyToManyField(RunasAlia, related_name='sudos', blank=True)
extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True)
privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True)
@property
def users(self):
ret = {}
for user in self.user_alias.all():
ret[user.name] = user.user_items.split(',')
return ret
@property
def commands(self):
ret = {}
for cmd in self.cmnd_alias.all():
ret[cmd.name] = cmd.cmd_items.split(',')
return ret
@property
def hosts(self):
ret = {}
for host in self.host_alias.all():
ret[host.name] = host.host_items.split(',')
return ret
@property
def runas(self):
ret = {}
for runas in self.runas_alias.all():
ret[runas.name] = runas.runas_items.split(',')
return ret
@property
def extras(self):
return [extra.line for extra in self.extra_lines.all()]
@property
def privileges(self):
return [privilege.to_tuple() for privilege in self.privilege_items.all()]
@property
def content(self):
template = Template(self.__sudoers_jinja2_tmp__)
context = {"User_Alias": self.users,
"Cmnd_Alias": self.commands,
"Host_Alias": self.hosts,
"Runas_Alias": self.runas,
"Extra_Lines": self.extras,
"Privileges": self.privileges}
return template.render(context)
@property
def __sudoers_jinja2_tmp__(self):
return """# management by JumpServer
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults env_reset
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# JumpServer Generate Other Configure is here
{% if Extra_Lines -%}
{% for line in Extra_Lines -%}
{{ line }}
{% endfor %}
{%- endif %}
# Host alias specification
{% if Host_Alias -%}
{% for flag, items in Host_Alias.iteritems() -%}
Host_Alias {{ flag }} = {{ items|join(', ') }}
{% endfor %}
{%- endif %}
# User alias specification
{% if User_Alias -%}
{% for flag, items in User_Alias.iteritems() -%}
User_Alias {{ flag }} = {{ items|join(', ') }}
{% endfor %}
{%- endif %}
# Cmnd alias specification
{% if Cmnd_Alias -%}
{% for flag, items in Cmnd_Alias.iteritems() -%}
Cmnd_Alias {{ flag }} = {{ items|join(', ') }}
{% endfor %}
{%- endif %}
# Run as alias specification
{% if Runas_Alias -%}
{% for flag, items in Runas_Alias.iteritems() -%}
Runas_Alias {{ flag }} = {{ items|join(', ') }}
{% endfor %}
{%- endif %}
# User privilege specification
root ALL=(ALL:ALL) ALL
# JumpServer Generate User privilege is here.
# Note privileges is a tuple list like [(user, host, runas, command, nopassword),]
{% if privileges -%}
{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in Privileges -%}
{% if NopassWord -%}
{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }}
{%- else -%}
{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }}
{%- endif %}
{% endfor %}
{%- endif %}
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
# See sudoers(5) for more information on "#include" directives:
#includedir /etc/sudoers.d
"""