diff --git a/flask-consul/manager.py b/flask-consul/manager.py index b0e6f09..66da848 100755 --- a/flask-consul/manager.py +++ b/flask-consul/manager.py @@ -6,8 +6,9 @@ skey_path = 'ConsulManager/assets/secret/skey' if consul_kv.get_kv_dict(skey_path) == {}: consul_kv.put_kv(skey_path,{'sk':''.join(str(uuid.uuid4()).split('-'))}) -from views import login, blackbox, consul, jobs, nodes, selfnode +from views import login, blackbox, consul, jobs, nodes, selfnode, avd from units.cloud import huaweicloud,alicloud,tencent_cloud +from units.avd import avd_list app = Flask(__name__) app.register_blueprint(login.blueprint) app.register_blueprint(blackbox.blueprint) @@ -15,13 +16,17 @@ app.register_blueprint(consul.blueprint) app.register_blueprint(jobs.blueprint) app.register_blueprint(nodes.blueprint) app.register_blueprint(selfnode.blueprint) +app.register_blueprint(avd.blueprint) class Config(object): JOBS = [] SCHEDULER_API_ENABLED = True init_jobs = consul_kv.get_kv_dict('ConsulManager/jobs') +avd_jobs = consul_kv.get_kv_dict('ConsulManager/avd/jobs') +init_jobs.update(avd_jobs) + if init_jobs is not None: for k,v in init_jobs.items(): - print(f'初始化任务:{k}:\n {v}', flush=True) + print(f'【初始化任务】{k}:\n {v}', flush=True) Config.JOBS = init_jobs.values() app.config.from_object(Config()) diff --git a/flask-consul/units/avd/avd_list.py b/flask-consul/units/avd/avd_list.py new file mode 100644 index 0000000..888dc36 --- /dev/null +++ b/flask-consul/units/avd/avd_list.py @@ -0,0 +1,58 @@ +import sys,requests,hashlib,json +from datetime import datetime +from bs4 import BeautifulSoup +from units import consul_kv + +def get_avd(): + avd_url = 'https://avd.aliyun.com' + res = requests.get(avd_url + '/high-risk/list') + res.encoding = 'utf-8' + soup = BeautifulSoup(res.text, 'html.parser') + bugs = soup.select('tr') + last_avd = consul_kv.get_value('ConsulManager/avd/list/0') + now = datetime.now().strftime('%Y-%m-%d') + if last_avd != {}: + del last_avd['avd_collect'] + for index, avd_info in enumerate(bugs[1:]): + avd = avd_info.select('td') + avd_dict = {} + avd_dict['avd_id'] = avd[0].getText(strip=True) + avd_dict['avd_id_url'] = avd_url + avd[0].a.attrs['href'] + avd_dict['avd_name'] = avd[1].getText(strip=True) + avd_dict['avd_type'] = avd[2].button.attrs.get('title',avd[2].getText(strip=True)) + avd_dict['avd_time'] = avd[3].getText(strip=True) + avd_dict['avd_stat'] = avd[4].select('button')[1].attrs['title'] + if index == 0 and avd_dict == last_avd: + print('【JOB】===>','avd_list','未采集到新漏洞。',flush=True) + break + else: + avd_dict['avd_collect'] = now + consul_kv.put_kv(f'ConsulManager/avd/list/{index}',avd_dict) + if index == 0: + print('【JOB】===>','avd_list',avd_dict,flush=True) + avd_switch = consul_kv.get_value('ConsulManager/avd/switch') + wecomwh = avd_switch.get('wecomwh','') + dingdingwh = avd_switch.get('dingdingwh','') + content = f"# {avd_dict['avd_name']}\n" \ + f"- 编号:{avd_dict['avd_id']}[【详情】]({avd_dict['avd_id_url']})\n" \ + f"- 类型:{avd_dict['avd_type']}\n" \ + f"- 披露:{avd_dict['avd_time']}\n" \ + f"- 状态:{avd_dict['avd_stat']}({avd_dict['avd_collect']})\n" + if avd_switch['switch'] and avd_switch['wecom'] and wecomwh.startswith('https://qyapi.weixin.qq.com'): + wecom(wecomwh,content) + if avd_switch['switch'] and avd_switch['dingding'] and dingdingwh.startswith('https://oapi.dingtalk.com'): + dingding(dingdingwh,content) + +def wecom(webhook,content): + headers = {'Content-Type': 'application/json'} + params = {'msgtype': 'markdown', 'markdown': {'content' : content}} + data = bytes(json.dumps(params), 'utf-8') + response = requests.post(webhook, headers=headers, data=data) + print('【wecom】',response.json(),flush=True) + +def dingding(webhook,content): + headers = {'Content-Type': 'application/json'} + params = {"msgtype":"markdown","markdown":{"title":"漏洞告警","text":content},"at":{"isAtAll":True}} + data = bytes(json.dumps(params), 'utf-8') + response = requests.post(webhook, headers=headers, data=data) + print('【dingding】',response.json(),flush=True) diff --git a/flask-consul/units/consul_kv.py b/flask-consul/units/consul_kv.py index 7cad9d5..2ca0e84 100644 --- a/flask-consul/units/consul_kv.py +++ b/flask-consul/units/consul_kv.py @@ -49,6 +49,13 @@ def del_key(path): return response.json() else: return None +def del_key_all(path): + url = f'{consul_url}/kv/{path}?recurse=true' + response = requests.delete(url, headers=headers) + if response.status_code == 200: + return response.json() + else: + return None def get_ecs_services(job_id): cloud,account,itype,region = job_id.split('/') diff --git a/flask-consul/views/avd.py b/flask-consul/views/avd.py new file mode 100644 index 0000000..d3d3fcf --- /dev/null +++ b/flask-consul/views/avd.py @@ -0,0 +1,52 @@ +from flask import Blueprint +from flask_restful import reqparse, Resource, Api +from flask_apscheduler import APScheduler +from units import token_auth,consul_kv +import json +from .jobs import deljob,addjob,runjob +blueprint = Blueprint('avd',__name__) +api = Api(blueprint) + +parser = reqparse.RequestParser() +parser.add_argument('avd_config_dict',type=dict) + +class Avd(Resource): + decorators = [token_auth.auth.login_required] + def get(self,stype): + if stype == 'list': + avd_dict = consul_kv.get_kv_dict('ConsulManager/avd/list') + avd_list = list(avd_dict.values()) + return {'code': 20000, 'avd_list': avd_list} + if stype == 'config': + avd_config = consul_kv.get_value('ConsulManager/avd/switch') + return {'code': 20000, 'avd_config': avd_config} + def post(self,stype): + if stype == 'config': + args = parser.parse_args() + avd_config_dict = args['avd_config_dict'] + consul_kv.put_kv('ConsulManager/avd/switch',avd_config_dict) + avd_job_id = 'avd_list' + avd_job_func = '__main__:avd_list.get_avd' + avd_job_args = [] + avd_job_interval = 60 + if avd_config_dict['switch']: + addjob(avd_job_id,avd_job_func,avd_job_args,avd_job_interval) + avd_job_dict = {'id':avd_job_id,'func':avd_job_func,'args':avd_job_args,'minutes':avd_job_interval, + 'trigger': 'interval','replace_existing': True} + consul_kv.put_kv('ConsulManager/avd/jobs/avd_list',avd_job_dict) + runjob(avd_job_id) + return {'code': 20000, 'data': '漏洞采集通知功能开启!'} + else: + deljob(avd_job_id) + consul_kv.del_key('ConsulManager/avd/jobs/avd_list') + consul_kv.del_key_all('ConsulManager/avd/list/') + return {'code': 20000, 'data': '漏洞采集通知功能关闭!'} + if stype == 'run': + avd_config_dict = consul_kv.get_value('ConsulManager/avd/switch') + if avd_config_dict['switch']: + consul_kv.del_key('ConsulManager/avd/list/0') + runjob('avd_list') + return {'code': 20000, 'data': '漏洞采集通知执行成功!'} + else: + return {'code': 50000, 'data': '漏洞采集功能未开启!'} +api.add_resource(Avd, '/api/avd/') diff --git a/flask-consul/views/jobs.py b/flask-consul/views/jobs.py index ab7eafe..a1259db 100644 --- a/flask-consul/views/jobs.py +++ b/flask-consul/views/jobs.py @@ -16,7 +16,16 @@ def init(): global Scheduler Scheduler = APScheduler() return Scheduler - + +def deljob(jobid): + Scheduler.remove_job(jobid) + +def addjob(job_id,job_func,job_args,job_interval): + Scheduler.add_job(id=job_id, func=job_func, args=job_args, trigger='interval', + minutes=job_interval, replace_existing=True) +def runjob(jobid): + Scheduler.run_job(jobid) + class Jobs(Resource): decorators = [token_auth.auth.login_required] def get(self): diff --git a/vue-consul/src/api/avd.js b/vue-consul/src/api/avd.js new file mode 100644 index 0000000..b451019 --- /dev/null +++ b/vue-consul/src/api/avd.js @@ -0,0 +1,29 @@ +import request from '@/utils/request-ops' + +export function getAvdList() { + return request({ + url: '/api/avd/list', + method: 'get' + }) +} +export function getAvdConfig() { + return request({ + url: '/api/avd/config', + method: 'get' + }) +} + +export function postAvdJob(avd_config_dict) { + return request({ + url: '/api/avd/config', + method: 'post', + data: { avd_config_dict } + }) +} + +export function postAvdRun() { + return request({ + url: '/api/avd/run', + method: 'post' + }) +} diff --git a/vue-consul/src/router/index.js b/vue-consul/src/router/index.js index ab6a0ea..1e9efc8 100644 --- a/vue-consul/src/router/index.js +++ b/vue-consul/src/router/index.js @@ -166,6 +166,16 @@ export const constantRoutes = [ } ] }, + { + path: '/avd', + component: Layout, + children: [{ + path: 'index', + name: '漏洞通知', + component: () => import('@/views/avd/index'), + meta: { title: '漏洞通知', icon: 'el-icon-chat-line-square' } + }] + }, { path: '快速链接', component: Layout, diff --git a/vue-consul/src/views/avd/index.vue b/vue-consul/src/views/avd/index.vue new file mode 100644 index 0000000..29704fe --- /dev/null +++ b/vue-consul/src/views/avd/index.vue @@ -0,0 +1,115 @@ + + + diff --git a/vue-consul/src/views/node-exporter/lists.vue b/vue-consul/src/views/node-exporter/lists.vue index a932b66..16f4c86 100644 --- a/vue-consul/src/views/node-exporter/lists.vue +++ b/vue-consul/src/views/node-exporter/lists.vue @@ -7,7 +7,7 @@ - +