add bug notify
This commit is contained in:
parent
a041e386c8
commit
798b345c33
|
@ -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())
|
||||
|
||||
|
|
|
@ -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"# <font color=\"#ff0000\">{avd_dict['avd_name']}</font>\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"- 状态:<font color=\"#ff0000\">{avd_dict['avd_stat']}</font>({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)
|
|
@ -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('/')
|
||||
|
|
|
@ -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/<stype>')
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">配置漏洞通知</el-button>
|
||||
<el-button class="filter-item" type="warning" icon="el-icon-magic-stick" @click="handleRun">执行一次</el-button>
|
||||
<el-dialog title="配置漏洞通知" :visible.sync="dialogFormVisible" width="45%">
|
||||
<el-form ref="dataForm" :model="avd_config" label-position="right" label-width="auto" style="width: 90%; margin-left: 20px;">
|
||||
<el-form-item label="漏洞采集">
|
||||
<el-switch v-model="avd_config.switch" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="avd_config.switch" label="钉钉通知">
|
||||
<el-switch v-model="avd_config.dingding" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="avd_config.switch && avd_config.dingding" required label="机器人Webhook地址">
|
||||
<el-input v-model="avd_config.dingdingwh" type="textarea" autosize />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="avd_config.switch" label="企业微信通知">
|
||||
<el-switch v-model="avd_config.wecom" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="avd_config.switch && avd_config.wecom" required label="机器人Webhook地址">
|
||||
<el-input v-model="avd_config.wecomwh" type="textarea" autosize />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="createData(avd_config)">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-table v-loading="listLoading" :data="avd_list" :default-sort="{ prop: 'avd_time', order: 'descending' }" border fit highlight-current-row style="width: 100%;">
|
||||
<el-table-column type="index" align="center" />
|
||||
<el-table-column prop="avd_id" label="AVD编号" sortable align="center" width="150" />
|
||||
<el-table-column prop="avd_name" label="漏洞名称" sortable align="center" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<el-link type="primary" style="font-weight:bold" :href="row.avd_id_url" target="_blank"><i class="el-icon-view el-icon--left" />{{ row.avd_name }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="avd_type" label="漏洞类型" sortable align="center" width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="avd_stat" label="漏洞状态" sortable align="center" width="160" />
|
||||
<el-table-column prop="avd_time" label="披露时间" sortable align="center" width="120" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAvdList, getAvdConfig, postAvdJob, postAvdRun } from '@/api/avd'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
listLoading: false,
|
||||
dialogFormVisible: false,
|
||||
avd_config: { switch: false, wecom: false, dingding: false, wecomwh: '', dingdingwh: '' },
|
||||
avd_list: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
handleCreate() {
|
||||
this.listLoading = true
|
||||
getAvdConfig().then(response => {
|
||||
this.avd_config = response.avd_config
|
||||
this.listLoading = false
|
||||
this.dialogFormVisible = true
|
||||
})
|
||||
},
|
||||
fetchData() {
|
||||
this.listLoading = true
|
||||
getAvdList().then(response => {
|
||||
this.avd_list = response.avd_list
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
createData(avd_config) {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.dialogFormVisible = false
|
||||
this.listLoading = true
|
||||
postAvdJob(avd_config).then(response => {
|
||||
this.fetchData()
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleRun() {
|
||||
this.$confirm('此操作将立刻执行一次漏洞通知,是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
postAvdRun().then(response => {
|
||||
this.fetchData()
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '操作已取消。'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -7,7 +7,7 @@
|
|||
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-refresh" circle @click="fetchEcs(jobecs_name)" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-table v-loading="listLoading" :data="ecs_list" :default-sort="{ prop: 'group', order: 'ascending' }" border fit highlight-current-row style="width: 100%;">
|
||||
<el-table v-loading="listLoading" :data="ecs_list" :default-sort="{ prop: 'exp', order: 'ascending' }" border fit highlight-current-row style="width: 100%;">
|
||||
<el-table-column type="index" align="center" />
|
||||
<el-table-column prop="group" label="分组" sortable align="center" width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="name" label="名称" sortable align="center" width="280" />
|
||||
|
|
Loading…
Reference in New Issue