Merge pull request #76 from netantho/averez-27-dashboards-listing

Dynamic Kibana dashboards listing
This commit is contained in:
jeffbryner 2014-04-17 12:50:50 -07:00
Родитель 82f489e6a6 5742b9f12f
Коммит 86cd2a9ca0
12 изменённых файлов: 292 добавлений и 182 удалений

Просмотреть файл

@ -191,12 +191,12 @@ You may want to edit the app/lib/settings.js file to properly point to your elas
Then start meteor with::
meteor
Node
******
Alternatively you can run the meteor UI in 'deployment' mode using a native node installation.
Alternatively you can run the meteor UI in 'deployment' mode using a native node installation.
First install node::
@ -206,31 +206,31 @@ First install node::
cd node-v0.10.25
python configure
make
make install
make install
Then bundle the meteor portion of mozdef::
cd <your meteor mozdef directory>
meteor bundle mozdef.tgz
You can then deploy the meteor UI for mozdef as necessary::
You can then deploy the meteor UI for mozdef as necessary::
scp mozdef.tgz to your target host
tar -xvzf mozdef.tgz
This will create a 'bundle' directory with the entire UI code below that directory.
You will need to update the settings.js file to match your servername/port::
vim bundle/programs/server/app/app/lib/settings.js
If your development OS is different than your production OS you will also need to update
the fibers node module::
the fibers node module::
cd bundle/programs/server/node_modules
rm -rf fibers
sudo npm install fibers@1.0.1
Then run the mozdef UI via node::
export MONGO_URL=mongodb://mongoservername:3002/meteor
@ -273,6 +273,8 @@ We use `uwsgi`_ to interface python and nginx::
cp uwsgi ~/envs/mozdef/bin/
cd rest
# modify settings.py
vim settings.py
# modify uwsgi.ini
vim uwsgi.ini
uwsgi --ini uwsgi.ini

Просмотреть файл

@ -6,13 +6,12 @@
standard-app-packages
autopublish
insecure
preserve-inputs
bootstrap
accounts-ui
accounts-password
backbone
iron-router
http
accounts-persona
d3
iron-router

1
meteor/.meteor/release Normal file
Просмотреть файл

@ -0,0 +1 @@
0.8.0

Просмотреть файл

@ -0,0 +1,52 @@
<!--
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) 2014 Mozilla Corporation
Contributors:
Jeff Bryner jbryner@mozilla.com
Anthony Verez averez@mozilla.com
-->
<template name="menu">
<div id="header" role="banner">
<h2><a class="mozdef" href="/" title="MOZDEF">MOZDEF</a></h2>
<div id="nav-main" role="navigation">
<ul>
<li class="first"><a href="#">Kibana dashboards</a>
<ul>
{{#each kibanadashboards as item}}
{{>kibanaDashboardItem}}
{{/each}}
</ul>
</li>
<li class="first"><a href="/events/">Events</a>
<ul>
<li class="first"><a href="/logincounts/">logincounts</a></li>
</ul>
</li>
<li><a href="/alerts/">Alerts</a>
<ul>
<li class="last"><a href="/alerts/setup">setup</a></li>
</ul>
</li>
<li><a href="/Incidents">Incidents</a>
<ul>
<li class="first"><a href="/incidents/new/">new incident</a></li>
<li class="last"><a href="/incidents/attackers">attackers</a></li>
</ul>
</li>
<li><a href="/about">About</a></li>
</ul>
<a class="mozilla" href="http://mozilla.org"><img src="/images/mozilla-tab.png"></a>
</div>
</div>
</template>
<template name="kibanaDashboardItem">
<li class="first">
<a href="{{url}}">{{name}}</a>
</li>
</template>

Просмотреть файл

@ -30,6 +30,12 @@ if (Meteor.isClient) {
}
});
// loads kibana dashboards
Template.menu.kibanadashboards = function() {
Meteor.call('loadKibanaDashboards');
return kibanadashboards.find();
}
//helper functions for handlebars
UI.registerHelper('now', function() {
return new Date();
@ -413,7 +419,7 @@ if (Meteor.isClient) {
.range([0, maxRadius]);
d3.json(mozdef.ldapLoginDataURL, function(error, jsondata) {
d3.json(mozdef.rootAPI + '/ldapLogins/' , function(error, jsondata) {
//console.log(jsondata)
r.domain([0, d3.max(jsondata, function(d) { return d.success+ d.failures; })])
jsondata.forEach(function(d){
@ -583,7 +589,7 @@ if (Meteor.isClient) {
.range([0, maxRadius]);
d3.json(mozdef.alertDataURL, function(error, jsondata) {
d3.json(mozdef.rootAPI + '/alerts/', function(error, jsondata) {
if (typeof console !== 'undefined')
console.log(error)
r.domain([0, d3.max(jsondata, function(d) { return d.count; })])

Просмотреть файл

@ -8,9 +8,11 @@ Contributors:
Jeff Bryner jbryner@mozilla.com
*/
//common collections used by clients/server
incidents=new Meteor.Collection("incidents");
events=new Meteor.Collection("events");
alerts=new Meteor.Collection("alerts");
eshealth=new Meteor.Collection("eshealth");
veris=new Meteor.Collection("veris");
// common collections used by clients/server
incidents = new Meteor.Collection("incidents");
events = new Meteor.Collection("events");
alerts = new Meteor.Collection("alerts");
eshealth = new Meteor.Collection("eshealth");
veris = new Meteor.Collection("veris");
kibanadashboards = new Meteor.Collection("kibanadashboards");

Просмотреть файл

@ -10,19 +10,16 @@ Jeff Bryner jbryner@mozilla.com
//configuration settings
elasticsearch={
address:"http://localhost:9200/",
healthurl:"_cluster/health",
docstatsurl:"_stats/docs"
elasticsearch = {
address: "http://localhost:9200/",
healthurl: "_cluster/health",
docstatsurl: "_stats/docs",
}
mozdef={
rootURL:"https://localhost",
port:"443",
ldapKibana:"http://localhost:9090/#/dashboard/elasticsearch/mozdef%20ldap%20dashboard",
eventsKibana:"http://localhost:9090/index.html#/dashboard/elasticsearch/Logstash%20Style%20Search",
alertsKibana:"http://localhost:9090/index.html#/dashboard/elasticsearch/Alerts",
ldapLoginDataURL:"http://localhost:8081/ldapLogins/", // rest server
alertDataURL:"http://localhost:8081/alerts/" // rest server
mozdef = {
rootURL: "https://localhost",
port: "443",
rootAPI: "http://localhost:8081"
}

Просмотреть файл

@ -10,43 +10,62 @@ Jeff Bryner jbryner@mozilla.com
//public functions
Meteor.methods({
'saySomething':saySomething,
'refreshESStatus':refreshESStatus
'saySomething': saySomething,
'refreshESStatus': refreshESStatus,
'loadKibanaDashboards': loadKibanaDashboards
});
function saySomething() {
console.log("something is said");
}
function saySomething(){
console.log("something is said")
function refreshESStatus() {
console.log('Refreshing elastic search cluster stats for: ' + elasticsearch.address);
var esHealthRequest = HTTP.get(elasticsearch.address + elasticsearch.healthurl);
if (esHealthRequest.statusCode==200 && esHealthRequest.data) {
//get doc count and add it to the health request data
var esDocStatsRequest = HTTP.get(elasticsearch.address + elasticsearch.docstatsurl);
if (esDocStatsRequest.statusCode==200 && esDocStatsRequest.data) {
//set the current doc stats
if (esDocStatsRequest["data"]["_all"]["total"]["docs"]) {
esHealthRequest["data"]["total_docs"] = esDocStatsRequest["data"]["_all"]["total"]["docs"].count;
}
else {
esHealthRequest["data"]["total_docs"] = 0;
}
console.log('Total Docs: ' + esHealthRequest["data"]["total_docs"]);
}
function refreshESStatus(){
console.log('Refreshing elastic search cluster stats for: ' + elasticsearch.address);
esHealthRequest=HTTP.get(elasticsearch.address + elasticsearch.healthurl)
if (esHealthRequest.statusCode==200 && esHealthRequest.data) {
//set current status of the elastic search cluster
console.log("Updating elastic search cluster health");
eshealth.remove({});
eshealth.insert(esHealthRequest["data"]);
console.log(esHealthRequest["data"]);
} else {
//note the error
console.log("Could not retrieve elastic search cluster health..check settings");
console.log(elasticsearch.address + elasticsearch.healthurl);
console.log("returned a " + esHealthRequest.statusCode);
console.log(esHealthRequest["data"]);
}
}
//get doc count and add it to the health request data
esDocStatsRequest=HTTP.get(elasticsearch.address + elasticsearch.docstatsurl)
if (esDocStatsRequest.statusCode==200 && esDocStatsRequest.data ) {
//set the current doc stats
if (esDocStatsRequest["data"]["_all"]["total"]["docs"]) {
esHealthRequest["data"]["total_docs"]=esDocStatsRequest["data"]["_all"]["total"]["docs"].count
}
else {
esHealthRequest["data"]["total_docs"] = 0
}
console.log('Total Docs: '+ esHealthRequest["data"]["total_docs"])
}
function loadKibanaDashboards() {
console.log('Loading Kibana dashboards... ' + mozdef.rootAPI + '/kibanadashboards');
var dashboardsRequest = HTTP.get(mozdef.rootAPI + '/kibanadashboards');
if (dashboardsRequest.statusCode==200 && dashboardsRequest.data) {
// set the current dashboards in the mongo collection
console.log("Updating kibana dashboards...");
kibanadashboards.remove({});
dashboardsRequest.data.forEach(function(dashboard, index, arr) {
kibanadashboards.insert(dashboard);
});
console.log(dashboardsRequest.data);
} else {
console.log("Could not retrieve kibana dashboards... check settings");
console.log(mozdef.rootAPI + '/kibanadashboards');
console.log("returned a " + dashboardsRequest.statusCode);
console.log(dashboardsRequest.data);
}
}
//set current status of the elastic search cluster
console.log("Updating elastic search cluster health")
eshealth.remove({})
eshealth.insert(esHealthRequest["data"])
console.log(esHealthRequest["data"])
}else{
//note the error
console.log("Could not retrieve elastic search cluster health..check settings")
console.log(elasticsearch.address + elasticsearch.healthurl)
console.log("returned a " + esHealthRequest.statusCode)
console.log(esHealthRequest["data"])
}
}

Просмотреть файл

@ -28,36 +28,7 @@ Anthony Verez averez@mozilla.com
<title>mozdef::mozilla defense platform</title>
</head>
<body>
<div id="header" role="banner">
<h2><a class="mozdef" href="/" title="MOZDEF">MOZDEF</a></h2>
<div id="nav-main" role="navigation">
<ul>
<li class="first"><a href="/events/">Events</a>
<ul>
<li class="first"><a href="/logincounts/">logincounts</a></li>
<li><a target=" _blank" href="{{mozdef.ldapKibana}}">ldap kibana</a></li>
<li class="last"><a target=" _blank" href="{{mozdef.eventsKibana}}">events kibana</a></li>
</ul>
</li>
<li ><a href="/alerts/">Alerts</a>
<ul>
<li class="first"><a target="_blank" href="{{mozdef.alertsKibana}}">alerts kibana</a></li>
<li class="last"><a href="/alerts/setup">setup</a></li>
</ul>
</li>
<li><a href="/Incidents">Incidents</a>
<ul>
<li class="first"><a href="/incidents/new/">new incident</a></li>
<li class="last"><a href="/incidents/attackers">attackers</a></li>
</ul>
</li>
<li><a href="/about">About</a></li>
</ul>
<a class="mozilla" href="http://mozilla.org"><img src="/images/mozilla-tab.png"></a>
</div>
</div>
{{> menu}}
<div class="loginButtons" >
{{#if loggingIn}}
Logging In
@ -81,10 +52,9 @@ Anthony Verez averez@mozilla.com
<template name="layout">
<div>
{{#if currentUser}}
{{>yield}}
{{/if}}
{{/if}}
</div>
</template>

Просмотреть файл

@ -1,6 +1,6 @@
{
"packages": {
"iron-router": {},
"accounts-persona": {}
"accounts-persona": {},
"iron-router": {}
}
}

Просмотреть файл

@ -2,19 +2,24 @@
"meteor": {},
"dependencies": {
"basePackages": {
"iron-router": {},
"accounts-persona": {}
"accounts-persona": {},
"iron-router": {}
},
"packages": {
"iron-router": {
"git": "https://github.com/EventedMind/iron-router.git",
"tag": "v0.6.2",
"commit": "ce56e58b45624dce992490f3a1c10f10d3948bae"
},
"accounts-persona": {
"git": "https://github.com/vladikoff/meteor-accounts-persona.git",
"tag": "v0.1.6",
"commit": "5b8110dad901a69f674e6a0381e074567fe1b4d9"
},
"iron-router": {
"git": "https://github.com/EventedMind/iron-router.git",
"tag": "v0.7.1",
"commit": "d1ffb3f06ea4c112132b030f2eb1a70b81675ecb"
},
"blaze-layout": {
"git": "https://github.com/EventedMind/blaze-layout.git",
"tag": "v0.2.4",
"commit": "b40e9b0612329288d75cf52ad14a7da64bb8618f"
}
}
}

Просмотреть файл

@ -5,13 +5,13 @@
#
# Contributors:
# Jeff Bryner jbryner@mozilla.com
# Anthony Verez averez@mozilla.com
import sys
import bottle
from bottle import debug,route, run, template, response,request,post, default_app
from bottle import _stdout as bottlelog
from bottle import debug,route, run, response, request, default_app
import json
from configlib import getConfig,OptionParser
from configlib import getConfig, OptionParser
import pyes
from elasticutils import S
from datetime import datetime
@ -19,8 +19,10 @@ from datetime import timedelta
from dateutil.parser import parse
import pytz
options=None
options = None
# cors decorator for rest/ajax
def enable_cors(fn):
def _enable_cors(*args, **kwargs):
# set CORS headers
@ -34,12 +36,14 @@ def enable_cors(fn):
return _enable_cors
@route('/test')
@route('/test/')
def index():
ip = request.environ.get('REMOTE_ADDR')
#response.headers['X-IP'] = '{0}'.format(ip)
response.status=200
response.status = 200
@route('/status')
@route('/status/')
@ -47,10 +51,11 @@ def index():
if request.body:
request.body.read()
request.body.close()
response.status=200
response.content_type="application/json"
response.status = 200
response.content_type = "application/json"
return(json.dumps(dict(status='ok')))
@route('/ldapLogins')
@route('/ldapLogins/')
@enable_cors
@ -58,8 +63,8 @@ def index():
if request.body:
request.body.read()
request.body.close()
response.content_type="application/json"
return(esLdapResults())
response.content_type = "application/json"
return(esLdapResults())
@route('/alerts')
@ -69,42 +74,55 @@ def index():
if request.body:
request.body.read()
request.body.close()
response.content_type="application/json"
return(esAlertsSummary())
response.content_type = "application/json"
return(esAlertsSummary())
@route('/kibanadashboards')
@route('/kibanadashboards/')
@enable_cors
def index():
if request.body:
request.body.read()
request.body.close()
response.content_type = "application/json"
return(kibanaDashboards())
#debug(True)
def toUTC(suspectedDate,localTimeZone="US/Pacific"):
def toUTC(suspectedDate, localTimeZone="US/Pacific"):
'''make a UTC date out of almost anything'''
utc=pytz.UTC
objDate=None
if type(suspectedDate)==str:
objDate=parse(suspectedDate,fuzzy=True)
elif type(suspectedDate)==datetime:
objDate=suspectedDate
utc = pytz.UTC
objDate = None
if type(suspectedDate) == str:
objDate = parse(suspectedDate, fuzzy=True)
elif type(suspectedDate) == datetime:
objDate = suspectedDate
if objDate.tzinfo is None:
objDate=pytz.timezone(localTimeZone).localize(objDate)
objDate=utc.normalize(objDate)
objDate = pytz.timezone(localTimeZone).localize(objDate)
objDate = utc.normalize(objDate)
else:
objDate=utc.normalize(objDate)
objDate = utc.normalize(objDate)
if objDate is not None:
objDate=utc.normalize(objDate)
objDate = utc.normalize(objDate)
return objDate
def esAlertsSummary(begindateUTC=None, enddateUTC=None):
resultsList=list()
resultsList = list()
if begindateUTC is None:
begindateUTC=datetime.now() - timedelta(hours=12)
begindateUTC=toUTC(begindateUTC)
begindateUTC = datetime.now() - timedelta(hours=12)
begindateUTC = toUTC(begindateUTC)
if enddateUTC is None:
enddateUTC= datetime.now()
enddateUTC= toUTC(enddateUTC)
enddateUTC = datetime.now()
enddateUTC = toUTC(enddateUTC)
try:
#q=S().es(urls=['http://{0}:{1}'.format(options.esserver,options.esport)]).query(_type='alert').filter(utctimestamp__range=[begindateUTC.isoformat(),enddateUTC.isoformat()])
#f=q.facet_raw(alerttype={"terms" : {"script_field" : "_source.type","size" : 500}})
#get all alerts
#q= S().es(urls=['http://{0}:{1}'.format(options.esserver,options.esport)]).query(_type='alert')
q= S().es(urls=list('{0}'.format(s) for s in options.esservers)).query(_type='alert')
@ -114,80 +132,119 @@ def esAlertsSummary(begindateUTC=None, enddateUTC=None):
"facet_filter":{'range': {'utctimestamp': \
{'gte': begindateUTC.isoformat(), 'lte': enddateUTC.isoformat()}}}\
})
})
return(json.dumps(f.facet_counts()['alerttype']))
except Exception as e :
sys.stderr.write('%r'%e)
except Exception as e:
sys.stderr.write('%r' % e)
def esLdapResults(begindateUTC=None, enddateUTC=None):
resultsList=list()
resultsList = list()
if begindateUTC is None:
begindateUTC=datetime.now() - timedelta(hours=12)
begindateUTC=toUTC(begindateUTC)
begindateUTC = datetime.now() - timedelta(hours=12)
begindateUTC = toUTC(begindateUTC)
if enddateUTC is None:
enddateUTC= datetime.now()
enddateUTC= toUTC(enddateUTC)
enddateUTC = datetime.now()
enddateUTC = toUTC(enddateUTC)
try:
es=pyes.ES((list('{0}'.format(s) for s in options.esservers)))
es = pyes.ES((list('{0}'.format(s) for s in options.esservers)))
#qDate=e=pyes.MatchQuery("message",options.datePhrase,"phrase")
qDate=pyes.RangeQuery(qrange=pyes.ESRange('utctimestamp',from_value=begindateUTC,to_value=enddateUTC))
qDate = pyes.RangeQuery(qrange=pyes.ESRange('utctimestamp',
from_value=begindateUTC, to_value=enddateUTC))
q = pyes.MatchAllQuery()
q = pyes.FilteredQuery(q,qDate)
q = pyes.FilteredQuery(q,pyes.TermFilter('tags','ldap'))
q = pyes.FilteredQuery(q,pyes.TermFilter('details.result','ldap_invalid_credentials'))
q2=q.search()
q = pyes.FilteredQuery(q, qDate)
q = pyes.FilteredQuery(q, pyes.TermFilter('tags', 'ldap'))
q = pyes.FilteredQuery(q,
pyes.TermFilter('details.result', 'ldap_invalid_credentials'))
q2 = q.search()
q2.facet.add_term_facet('details.result')
q2.facet.add_term_facet('details.dn',size=20)
results=es.search(q2, indices='events')
q2.facet.add_term_facet('details.dn', size=20)
results = es.search(q2, indices='events')
#sys.stdout.write('{0}\n'.format(results.facets))
stoplist=('o','mozilla','dc','com','mozilla.com','mozillafoundation.org','org')
stoplist = ('o', 'mozilla', 'dc', 'com', 'mozilla.com',
'mozillafoundation.org', 'org')
for t in results.facets['details.dn'].terms:
if t['term'] in stoplist:
continue
#print(t['term'])
failures=0
success=0
dn=t['term']
failures = 0
success = 0
dn = t['term']
#re-query with the terms of the details.dn
qt = pyes.MatchAllQuery()
qt = pyes.FilteredQuery(qt,qDate)
qt = pyes.FilteredQuery(qt,pyes.TermFilter('tags','ldap'))
qt = pyes.FilteredQuery(qt,pyes.TermFilter('details.dn',t['term']))
qt2=qt.search()
qt = pyes.FilteredQuery(qt, qDate)
qt = pyes.FilteredQuery(qt, pyes.TermFilter('tags', 'ldap'))
qt = pyes.FilteredQuery(qt,
pyes.TermFilter('details.dn', t['term']))
qt2 = qt.search()
qt2.facet.add_term_facet('details.result')
results=es.search(qt2)
results = es.search(qt2)
#sys.stdout.write('{0}\n'.format(results.facets['details.result'].terms))
for t in results.facets['details.result'].terms:
#print(t['term'],t['count'])
if t['term']=='ldap_success':
success=t['count']
if t['term']=='ldap_invalid_credentials':
failures=t['count']
resultsList.append(dict(dn=dn,failures=failures,success=success,begin=begindateUTC.isoformat(),end=enddateUTC.isoformat()))
if t['term'] == 'ldap_success':
success = t['count']
if t['term'] == 'ldap_invalid_credentials':
failures = t['count']
resultsList.append(dict(dn=dn, failures=failures,
success=success, begin=begindateUTC.isoformat(),
end=enddateUTC.isoformat()))
return(json.dumps(resultsList))
except pyes.exceptions.NoServerAvailable:
sys.stderr.write('Elastic Search server could not be reached, check network connectivity\n')
def kibanaDashboards():
try:
resultsList = []
es = pyes.ES((list('{0}'.format(s) for s in options.esservers)))
r = es.search(pyes.Search(pyes.MatchAllQuery(), size=100),
'kibana-int', 'dashboard')
if r:
for dashboard in r:
dashboardJson = json.loads(dashboard.dashboard)
resultsList.append({
'name': dashboardJson['title'],
'url': "%s/%s/%s" % (options.kibanaurl,
"index.html#/dashboard/elasticsearch",
dashboardJson['title'])
})
return json.dumps(resultsList)
else:
sys.stderr.write('No Kibana dashboard found\n')
except pyes.exceptions.NoServerAvailable:
sys.stderr.write('Elastic Search server could not be reached, check network connectivity\n')
def initConfig():
#change this to your default zone for when it's not specified
options.defaultTimeZone=getConfig('defaulttimezone','US/Pacific',options.configfile)
options.esservers=list(getConfig('esservers','http://localhost:9200',options.configfile).split(','))
options.defaultTimeZone = getConfig('defaulttimezone', 'US/Pacific',
options.configfile)
options.esservers = list(getConfig('esservers', 'http://localhost:9200',
options.configfile).split(','))
options.kibanaurl = getConfig('kibanaurl', 'http://localhost:9090',
options.configfile)
print(options)
if __name__ == "__main__":
parser=OptionParser()
parser.add_option("-c", dest='configfile' , default=sys.argv[0].replace('.py','.conf'), help="configuration file to use")
(options,args) = parser.parse_args()
parser = OptionParser()
parser.add_option("-c", dest='configfile',
default=sys.argv[0].replace('.py', '.conf'),
help="configuration file to use")
(options, args) = parser.parse_args()
initConfig()
run(host="localhost", port=8081)
else:
parser=OptionParser()
parser.add_option("-c", dest='configfile' , default=sys.argv[0].replace('.py','.conf'), help="configuration file to use")
(options,args) = parser.parse_args()
initConfig()
parser = OptionParser()
parser.add_option("-c", dest='configfile',
default=sys.argv[0].replace('.py', '.conf'),
help="configuration file to use")
(options, args) = parser.parse_args()
initConfig()
application = default_app()