def post(self, request): form, error = JsonParser(Argument('data', type=list, help='缺少必要的参数')).parse(request.body) if error is None: for item in form.data: AppSetting.set(**item) return json_response(error=error)
def handle(self, *args, **options): target = options['target'] if target == 'mfa': if options['value'] != 'disable': return self.echo_error(f'mfa设置,不支持的值【{options["value"]}】') AppSetting.set('MFA', {'enable': False}) self.echo_success('MFA已禁用') else: self.echo_error('未识别的操作') self.print_help()
def notify_by_email(event, subject, grp): spug_key, u_ids = _parse_args(grp) users = set( x.email for x in Contact.objects.filter(id__in=u_ids, email__isnull=False)) if users: mail_service = json.loads(AppSetting.get_default('mail_service', '{}')) if mail_service.get('server'): event_map = {'1': '告警', '2': '恢复'} subject = f'{event_map[event]}-{subject}' mail = Mail(**mail_service) mail.send_text_mail(users, subject, f'{subject}\r\n\r\n自动发送,请勿回复。') elif spug_key: data = { 'token': spug_key, 'event': event, 'subject': subject, 'users': list(users) } requests.post(f'{spug_server}/apis/notify/mail/', json=data) else: Notify.make_notify(notify_source, '1', '发送报警信息失败', '未配置报警服务调用凭据,请在系统管理/系统设置/报警服务设置中配置。') else: Notify.make_notify(notify_source, '1', '发送报警信息失败', '未找到可用的通知对象,请确保设置了相关报警联系人的邮件地址。')
def monitor_by_email(self, users): mail_service = AppSetting.get_default('mail_service', {}) body = [ f'告警名称:{self.title}', f'告警对象:{self.target}', f'{"告警" if self.event == "1" else "恢复"}时间:{human_datetime()}', f'告警描述:{self.message}' ] if self.event == '2': body.append('故障持续:' + self.duration) if mail_service.get('server'): event_map = {'1': '监控告警通知', '2': '告警恢复通知'} subject = f'{event_map[self.event]}-{self.title}' mail = Mail(**mail_service) mail.send_text_mail(users, subject, '\r\n'.join(body) + '\r\n\r\n自动发送,请勿回复。') elif self.spug_key: data = { 'token': self.spug_key, 'event': self.event, 'subject': self.title, 'body': '\r\n'.join(body), 'users': list(users) } self.handle_request(f'{spug_server}/apis/notify/mail/', data, 'spug') else: Notify.make_monitor_notify('发送报警信息失败', '未配置报警服务调用凭据,请在系统管理/系统设置/报警服务设置中配置。')
def handle_user_info(user, x_real_ip): cache.delete(user.username) token_isvalid = user.access_token and len( user.access_token) == 32 and user.token_expired >= time.time() user.access_token = user.access_token if token_isvalid else uuid.uuid4( ).hex user.token_expired = time.time() + 8 * 60 * 60 user.last_login = human_datetime() user.last_ip = x_real_ip user.save() History.objects.create(user=user, ip=x_real_ip) verify_ip = AppSetting.get_default('verify_ip', 'True') == 'True' return json_response({ 'access_token': user.access_token, 'nickname': user.nickname, 'is_supper': user.is_supper, 'has_real_ip': x_real_ip and ipaddress.ip_address(x_real_ip).is_global if verify_ip else True, 'host_perms': [] if user.is_supper else user.host_perms, 'permissions': [] if user.is_supper else user.page_perms })
def notify_by_email(event, obj): spug_key, u_ids = _parse_args(obj.grp) users = set( x.email for x in Contact.objects.filter(id__in=u_ids, email__isnull=False)) if users: mail_service = json.loads(AppSetting.get_default('mail_service', '{}')) body = [ '告警名称:' + obj.name, '告警时间:' + human_datetime(), '告警描述:' + obj.out ] if event == '2': body.append('故障持续:' + obj.duration) if mail_service.get('server'): event_map = {'1': '告警发生', '2': '告警恢复'} subject = f'{event_map[event]}-{obj.name}' mail = Mail(**mail_service) mail.send_text_mail(users, subject, '\r\n'.join(body) + '\r\n\r\n自动发送,请勿回复。') elif spug_key: data = { 'token': spug_key, 'event': event, 'subject': obj.name, 'body': '\r\n'.join(body), 'users': list(users) } requests.post(f'{spug_server}/apis/notify/mail/', json=data) else: Notify.make_notify(notify_source, '1', '发送报警信息失败', '未配置报警服务调用凭据,请在系统管理/系统设置/报警服务设置中配置。') else: Notify.make_notify(notify_source, '1', '发送报警信息失败', '未找到可用的通知对象,请确保设置了相关报警联系人的邮件地址。')
def connect(self): self.accept() self.user = self.scope["user"] print(self.scope) query_string = self.scope.get("query_string") print(query_string) ssh_args = QueryDict(query_string=query_string, encoding='utf-8') host = ssh_args.get('hostname') host = Host.objects.get(hostname=host, deleted_by_id__isnull=True) exec = ssh_args.get("exec").replace(',', "\n") ssh_connect_dict = { 'host': host.hostname, 'user': host.username, 'port': int(host.port), 'exec': exec, } if host.pkey == '': private_key = AppSetting.get('private_key') ssh_connect_dict['ssh_key'] = private_key else: ssh_connect_dict['ssh_key'] = host.pkey self.ssh = SSHExecutor(websocker=self, **ssh_connect_dict) threading.Thread(target=self.ssh.run()).start()
def connect(self): """ 打开 websocket 连接, 通过前端传入的参数尝试连接 ssh 主机 :return: """ self.accept() query_string = self.scope.get('query_string') ssh_args = QueryDict(query_string=query_string, encoding='utf-8') width = int(ssh_args.get('width')) height = int(ssh_args.get('height')) id = ssh_args.get('id') host = Host.objects.get(id=id) self.ssh = SSH(websocker=self, message=self.message) ssh_connect_dict = { 'host': host.hostname, 'user': host.username, 'port': host.port, 'timeout': 30, 'pty_width': width, 'pty_height': height, } if host.pkey == '': private_key = AppSetting.get('private_key') ssh_connect_dict['ssh_key'] = private_key else: ssh_connect_dict['ssh_key'] = host.pkey self.ssh.connect(**ssh_connect_dict)
def handle_user_info(request, user, captcha): cache.delete(user.username) key = f'{user.username}:code' if captcha: code = cache.get(key) if not code: return json_response(error='验证码已失效,请重新获取') if code != captcha: ttl = cache.ttl(key) cache.expire(key, ttl - 100) return json_response(error='验证码错误') cache.delete(key) else: mfa = AppSetting.get_default('MFA', {'enable': False}) if mfa['enable']: if not user.wx_token: return json_response(error='已启用登录双重认证,但您的账户未配置微信Token,请联系管理员') code = generate_random_str(6) send_login_wx_code(user.wx_token, code) cache.set(key, code, 300) return json_response({'required_mfa': True}) x_real_ip = get_request_real_ip(request.headers) token_isvalid = user.access_token and len( user.access_token) == 32 and user.token_expired >= time.time() user.access_token = user.access_token if token_isvalid else uuid.uuid4( ).hex user.token_expired = time.time() + 8 * 60 * 60 user.last_login = human_datetime() user.last_ip = x_real_ip user.save() History.objects.create(user=user, ip=x_real_ip) verify_ip = AppSetting.get_default('verify_ip', True) return json_response({ 'id': user.id, 'access_token': user.access_token, 'nickname': user.nickname, 'is_supper': user.is_supper, 'has_real_ip': x_real_ip and ipaddress.ip_address(x_real_ip).is_global if verify_ip else True, 'permissions': [] if user.is_supper else list(user.page_perms) })
def _parse_args(grp): spug_key = AppSetting.get_default('spug_key') if not spug_key: Notify.make_notify(notify_source, '1', '发送报警信息失败', '未配置报警服务调用凭据,请在系统管理/系统设置/报警服务设置中配置。') return None, None return spug_key, sum( [json.loads(x.contacts) for x in Group.objects.filter(id__in=grp)], [])
def send_login_wx_code(wx_token, code): url = f'{spug_server}/apis/login/wx/' spug_key = AppSetting.get_default('spug_key') res = requests.post(url, json={'token': spug_key, 'user': wx_token, 'code': code}, timeout=30) if res.status_code != 200: raise Exception(f'status code: {res.status_code}') res = res.json() if res.get('error'): raise Exception(res['error'])
def __init__(self, grp, event, target, title, message, duration): self.grp = grp self.event = event self.title = title self.target = target self.message = message self.duration = duration self.spug_key = AppSetting.get_default('spug_key') self.u_ids = []
def fetch_versions(deploy: Deploy): git_repo = deploy.extend_obj.git_repo repo_dir = os.path.join(settings.REPOS_DIR, str(deploy.id)) try: pkey = AppSetting.get('private_key') except KeyError: pkey = None with Git(git_repo, repo_dir, pkey) as git: return git.fetch_branches_tags()
def post(self, request): form, error = JsonParser( Argument('enable', type=bool, help='参数错误'), Argument('code', required=False) ).parse(request.body) if error is None: if form.enable: if not form.code: return json_response(error='请输入验证码') key = f'{request.user.username}:code' code = cache.get(key) if not code: return json_response(error='验证码已失效,请重新获取') if code != form.code: ttl = cache.ttl(key) cache.expire(key, ttl - 100) return json_response(error='验证码错误') cache.delete(key) AppSetting.set('MFA', {'enable': form.enable}) return json_response(error=error)
def valid_ssh(hostname, port, username, password, with_expect=True): try: private_key = AppSetting.get('private_key') #定义私钥公钥 public_key = AppSetting.get('public_key') except KeyError: #错误检查 private_key, public_key = SSH.generate_key() AppSetting.set('private_key', private_key, 'ssh private key') AppSetting.set('public_key', public_key, 'ssh public key') cli = SSH(hostname, port, username, private_key) if password: _cli = SSH(hostname, port, username, password=str(password)) code, out = _cli.exec_command('mkdir -p -m 700 ~/.ssh && \ echo %r >> ~/.ssh/authorized_keys && \ chmod 600 ~/.ssh/authorized_keys' % public_key) if code != 0: raise Exception(f'add public key error: {out!r}') try: cli.ping() except BadAuthenticationType: if with_expect: raise TypeError('该主机不支持密钥认证,请参考官方文档,错误代码:E01') return False except AuthenticationException: if password and with_expect: raise ValueError('密钥认证失败,请参考官方文档,错误代码:E02') return False return True
def valid_ssh(hostname, port, username, password=None, pkey=None, with_expect=True): try: private_key = AppSetting.get('private_key') public_key = AppSetting.get('public_key') except KeyError: private_key, public_key = SSH.generate_key() AppSetting.set('private_key', private_key, 'ssh private key') AppSetting.set('public_key', public_key, 'ssh public key') if password: _cli = SSH(hostname, port, username, password=str(password)) _cli.add_public_key(public_key) if pkey: private_key = pkey try: cli = SSH(hostname, port, username, private_key) cli.ping() except BadAuthenticationType: if with_expect: raise TypeError('该主机不支持密钥认证,请参考官方文档,错误代码:E01') return False except AuthenticationException: if password and with_expect: raise ValueError('密钥认证失败,请参考官方文档,错误代码:E02') return False return True
def valid_ssh(hostname, port, username, password=None, pkey=None, with_expect=True): try: private_key = AppSetting.get('private_key') public_key = AppSetting.get('public_key') except KeyError: private_key, public_key = SSH.generate_key() AppSetting.set('private_key', private_key, 'ssh private key') AppSetting.set('public_key', public_key, 'ssh public key') try: if password: _cli = SSH(hostname, port, username, password=str(password)) _cli.add_public_key(public_key) if pkey: private_key = pkey cli = SSH(hostname, port, username, private_key) cli.ping() except BadAuthenticationType: if with_expect: data = '该主机不支持密钥认证,错误代码:E01' else: data = '该主机不支持密钥认证,错误代码:E02' return False, 500, data except AuthenticationException: if password and with_expect: data = '密钥认证失败,错误代码:E03' else: data = "密钥认证失败,错误代码:E04" return False, 500, data return True, 200, {'msg': "认证成功"}
def verify_user(self, scope, headers): close_old_connections() query_string = scope['query_string'].decode() x_real_ip = self.get_real_ip(headers) token = parse_qs(query_string).get('x-token', [''])[0] if token and len(token) == 32: user = User.objects.filter(access_token=token).first() if user and user.token_expired >= time.time() and user.is_active: if x_real_ip == user.last_ip or AppSetting.get_default( 'bind_ip') is False: scope['user'] = user return True, None return False, f'Verify failed: {x_real_ip} <> {user.last_ip if user else None}' return False, 'Token is invalid'
def dispatch(tp, addr, extra): if tp == '1': return site_check(addr) elif tp == '2': return port_check(addr, extra) elif tp == '3': command = f'ps -ef|grep -v grep|grep {extra!r}' elif tp == '4': command = extra else: raise TypeError(f'invalid monitor type: {tp!r}') pkey = AppSetting.get('private_key') host = Host.objects.filter(pk=addr).first() return host_executor(host, pkey, command)
def _init(self): self.send(bytes_data=b'Connecting ...\r\n') host = Host.objects.filter(pk=self.id).first() if not host: self.send(text_data='Unknown host\r\n') self.close() try: self.ssh = host.get_ssh(AppSetting.get('private_key')).get_client() except Exception as e: self.send(bytes_data=f'Exception: {e}\r\n'.encode()) self.close() self.chan = self.ssh.invoke_shell(term='xterm') self.chan.transport.set_keepalive(30) Thread(target=self.loop_read).start()
def dispatch(command, targets): close_old_connections() threads, pkey, q = [], AppSetting.get('private_key'), Queue() for t in targets: if t == 'local': threads.append(Thread(target=local_executor, args=(q, command))) elif isinstance(t, int): host = Host.objects.filter(pk=t).first() if not host: raise ValueError(f'unknown host id: {t!r}') threads.append(Thread(target=host_executor, args=(q, host, pkey, command))) else: raise ValueError(f'invalid target: {t!r}') for t in threads: t.start() return [q.get() for _ in threads]
def post(self, request): form, error = JsonParser( Argument('id', type=int, required=False), Argument('group_ids', type=list, filter=lambda x: len(x), help='请选择主机分组'), Argument('name', help='请输主机名称'), Argument('username', handler=str.strip, help='请输入登录用户名'), Argument('hostname', handler=str.strip, help='请输入主机名或IP'), Argument('port', type=int, help='请输入SSH端口'), Argument('pkey', required=False), Argument('desc', required=False), Argument('password', required=False), ).parse(request.body) if error is None: password = form.pop('password') private_key, public_key = AppSetting.get_ssh_key() try: if form.pkey: private_key = form.pkey elif password: with SSH(form.hostname, form.port, form.username, password=password) as ssh: ssh.add_public_key(public_key) with SSH(form.hostname, form.port, form.username, private_key) as ssh: ssh.ping() except BadAuthenticationType: return json_response(error='该主机不支持密钥认证,请参考官方文档,错误代码:E01') except AuthenticationException: if password: return json_response(error='密钥认证失败,请参考官方文档,错误代码:E02') return json_response('auth fail') group_ids = form.pop('group_ids') other = Host.objects.filter(name=form.name).first() if other and (not form.id or other.id != form.id): return json_response(error=f'已存在的主机名称【{form.name}】') if form.id: Host.objects.filter(pk=form.id).update(is_verified=True, **form) host = Host.objects.get(pk=form.id) else: host = Host.objects.create(created_by=request.user, is_verified=True, **form) _sync_host_extend(host, ssh=ssh) host.groups.set(group_ids) response = host.to_view() response['group_ids'] = group_ids return json_response(response) return json_response(error=error)
def post(self, request): form, error = JsonParser( Argument('id', type=int, required=False), Argument('name', help='请输入任务名称'), Argument('group', help='请选择任务分组'), Argument('targets', type=list, filter=lambda x: len(x), help='请输入监控地址'), Argument('type', filter=lambda x: x in dict(Detection.TYPES), help='请选择监控类型'), Argument('extra', required=False), Argument('desc', required=False), Argument('rate', type=int, default=5), Argument('threshold', type=int, default=3), Argument('quiet', type=int, default=24 * 60), Argument('notify_grp', type=list, help='请选择报警联系组'), Argument('notify_mode', type=list, help='请选择报警方式'), ).parse(request.body) if error is None: if set(form.notify_mode).intersection(['1', '2', '4']): if not AppSetting.get_default('spug_key'): return json_response( error='报警方式 微信、短信、邮件需要配置调用凭据(系统设置/基本设置),请配置后再启用该报警方式。') form.targets = json.dumps(form.targets) form.notify_grp = json.dumps(form.notify_grp) form.notify_mode = json.dumps(form.notify_mode) if form.id: Detection.objects.filter(pk=form.id).update( updated_at=human_datetime(), updated_by=request.user, **form) task = Detection.objects.filter(pk=form.id).first() if task and task.is_active: form.action = 'modify' rds_cli = get_redis_connection() rds_cli.lpush(settings.MONITOR_KEY, json.dumps(form)) else: dtt = Detection.objects.create(created_by=request.user, **form) form.action = 'add' form.id = dtt.id rds_cli = get_redis_connection() rds_cli.lpush(settings.MONITOR_KEY, json.dumps(form)) return json_response(error=error)
def batch_sync_host(token, hosts, password): private_key, public_key = AppSetting.get_ssh_key() threads, latest_exception, rds = [], None, get_redis_connection() max_workers = max(10, os.cpu_count() * 5) with futures.ThreadPoolExecutor(max_workers=max_workers) as executor: for host in hosts: t = executor.submit(_sync_host_extend, host, private_key, public_key, password) t.host = host threads.append(t) for t in futures.as_completed(threads): exception = t.exception() if exception: rds.rpush(token, json.dumps({'key': t.host.id, 'status': 'fail', 'message': f'{exception}'})) else: rds.rpush(token, json.dumps({'key': t.host.id, 'status': 'ok'})) t.host.is_verified = True t.host.save() rds.expire(token, 60)
def _is_valid_token(request): api_key = AppSetting.get_default('api_key') # Gitlab Gitee Aliyun(Codeup) token = request.headers.get('X-Gitlab-Token') token = token or request.headers.get('X-Gitee-Token') token = token or request.headers.get('X-Codeup-Token') # Compatible the old version of gitlab token = token or request.GET.get('token') if token: return token == api_key # Gogs Github token = request.headers.get('X-Gogs-Signature') token = token or request.headers.get('X-Hub-Signature-256', '').replace( 'sha256=', '') if token: return token == hmac.new(api_key.encode(), request.body, hashlib.sha256).hexdigest() return False
def _parse_params(request): app, env_id = None, None api_token = request.GET.get('apiToken') if api_token: rds = get_redis_connection() content = rds.get(api_token) if content: app_id, env_id = content.decode().split(',') app = App.objects.filter(pk=app_id).first() else: api_key = AppSetting.get_default('api_key') if api_key and request.GET.get('apiKey') == api_key: app_key = request.GET.get('app') env_key = request.GET.get('env') if app_key and env_key: app = App.objects.filter(key=app_key).first() env = Environment.objects.filter(key=env_key).first() if env: env_id = env.id return app, env_id, request.GET.get('noPrefix')
def notify_by_email(event, subject, grp): spug_key, u_ids = _parse_args(grp) if u_ids is None: return users = set( x.email for x in Contact.objects.filter(id__in=u_ids, email__isnull=False)) if users: mail_service = json.loads(AppSetting.get_default('mail_service', '{}')) if mail_service.get('server'): event_map = {'1': '告警', '2': '恢复'} subject = f'{event_map[event]}-{subject}' mail = Mail(**mail_service) mail.send_text_mail(users, subject, f'{subject}\r\n\r\n自动发送,请勿回复。') else: data = { 'token': spug_key, 'event': event, 'subject': subject, 'users': list(users) } requests.post(f'{spug_server}/apis/notify/mail/', json=data)
def login(request): form, error = JsonParser(Argument('username', help='请输入用户名'), Argument('password', help='请输入密码'), Argument('captcha', required=False), Argument('type', required=False)).parse(request.body) if error is None: user = User.objects.filter(username=form.username, type=form.type).first() if user and not user.is_active: return json_response(error="账户已被系统禁用") if form.type == 'ldap': config = AppSetting.get_default('ldap_service') if not config: return json_response(error='请在系统设置中配置LDAP后再尝试通过该方式登录') ldap = LDAP(**config) is_success, message = ldap.valid_user(form.username, form.password) if is_success: if not user: user = User.objects.create(username=form.username, nickname=form.username, type=form.type) return handle_user_info(request, user, form.captcha) elif message: return json_response(error=message) else: if user and user.deleted_by is None: if user.verify_password(form.password): return handle_user_info(request, user, form.captcha) value = cache.get_or_set(form.username, 0, 86400) if value >= 3: if user and user.is_active: user.is_active = False user.save() return json_response(error='账户已被系统禁用') cache.set(form.username, value + 1, 86400) return json_response(error="用户名或密码错误,连续多次错误账户将会被禁用") return json_response(error=error)
def process_request(self, request): if request.path in settings.AUTHENTICATION_EXCLUDES: return None if any( x.match(request.path) for x in settings.AUTHENTICATION_EXCLUDES if hasattr(x, 'match')): return None access_token = request.headers.get('x-token') or request.GET.get( 'x-token') if access_token and len(access_token) == 32: x_real_ip = get_request_real_ip(request.headers) user = User.objects.filter(access_token=access_token).first() if user and user.token_expired >= time.time() and user.is_active: if x_real_ip == user.last_ip or AppSetting.get_default( 'bind_ip') is False: request.user = user user.token_expired = time.time() + 8 * 60 * 60 user.save() return None response = json_response(error="验证失败,请重新登录") response.status_code = 401 return response
def valid_ssh(hostname, port, username, password): try: private_key = AppSetting.get('private_key') public_key = AppSetting.get('public_key') except KeyError: private_key, public_key = SSH.generate_key() AppSetting.set('private_key', private_key, 'ssh private key') AppSetting.set('public_key', public_key, 'ssh public key') if password: cli = SSH(hostname, port, username, password=str(password)) code, out = cli.exec_command('mkdir -p -m 700 ~/.ssh && \ echo %r >> ~/.ssh/authorized_keys && \ chmod 600 ~/.ssh/authorized_keys' % public_key) if code != 0: raise Exception(f'add public key error: {out!r}') else: cli = SSH(hostname, port, username, private_key) try: cli.ping() except AuthenticationException: return False return True