Skip to content

Commit

Permalink
Initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Spencer committed Feb 20, 2013
0 parents commit d3e2f41
Show file tree
Hide file tree
Showing 10 changed files with 914 additions and 0 deletions.
Binary file added Halo-Event-Connector.pdf
Binary file not shown.
25 changes: 25 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,25 @@
Copyright (c) 2013, CloudPassage, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the CloudPassage, Inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL CLOUDPASSAGE, INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29 changes: 29 additions & 0 deletions README.md
@@ -0,0 +1,29 @@
Halo Event Connector Script - Python
==================
For sake of brevity, the document “Halo Event Connector” only covers integrating Halo events with the Splunk Enterprise Server SIEM tool.

However, given the number of different output formats the script supports, you will just as easily be able to integrate Halo events into other popular SIEM tools, such as ArcSight and Sumo Logic to name a few, or with your Syslog infrastructure.

In addition, there are several ways you can run this script to stream event data to your desired target.

For example, let’s say, you wanted to setup this script to be run from cron, emit Halo events as key-value name pairs and append them to a file on the local filesystem. And you wanted to pull only those events that were logged since Nov 10, 2012 onwards. And instead of using the script defaults where the files are expected to be in the program directory, let’s say you wanted to use a different working directory /opt/cloudpassage, for example.

For that, you would do something like this:

Run crontab -e and add a line with the desired schedule, such as the following to run, say every 5 minutes

```
*/5 * * * * /opt/cloudpassage/bin/haloEvents.py --starting=2012-11-10 --auth=/opt/cloudpassage/config/myHaloKeys.auth --configdir=/opt/cloudpassage/config --kvfile=/opt/cloudpassage/logs/eventsInKVFormat >/dev/null 2>&1
```

Save your changes before you exit.

If you are extracting events from more than one (supports up to 5) Halo account, you can specify those in your myHaloKeys.auth file like this:

```
key_id_1|secret_1
key_id_2|secret_2
key_id_5|secret_5
```
224 changes: 224 additions & 0 deletions cpapi.py
@@ -0,0 +1,224 @@
#!/usr/bin/env python

import sys
import json
import urllib
import urllib2
import base64


# Class with calls to CloudPassage API
class CPAPI:
def __init__(self):
self.auth_url = 'oauth/access_token'
self.auth_args = {'grant_type': 'client_credentials'}
self.base_url = 'https://api.cloudpassage.com'
self.api_ver = 'v1'
self.port = 443
self.key_id = None
self.secret = None
self.authToken = None

# Dump debug info
def dumpToken(self, token, expires):
if (token):
print "AuthToken=%s" % token
if (expires):
print "Expires in %s minutes" % (expires / 60)

def getHttpStatus(self, code):
if (code == 200):
return "OK" # should never be passed in, only errors
elif (code == 401):
return "Unauthorized"
elif (code == 403):
return "Forbidden"
elif (code == 404):
return "Not found"
elif (code == 422):
return "Validation failed"
elif (code == 500):
return "Internal server error"
elif (code == 502):
return "Gateway error"
else:
return "Unknown code [%d]" % code

def addAuth(self, req, kid, sec):
combined = kid + ":" + sec
encoded = base64.b64encode(combined)
req.add_header("Authorization", "Basic " + encoded)

def getAuthToken(self, url, args, kid, sec):
req = urllib2.Request(url)
self.addAuth(req, kid, sec)
# print >> sys.stderr, "getAuthToken: key=%s secret=%s" % (kid, sec)
# createPasswordMgr(url, kid, sec)
if (args):
args = urllib.urlencode(args)
try:
fh = urllib2.urlopen(req, args)
return fh.read()
except IOError, e:
if hasattr(e, 'reason'):
print >> sys.stderr, "Failed to connect [%s] to '%s'" % (e.reason, url)
elif hasattr(e, 'code'):
msg = self.getHttpStatus(e.code)
print >> sys.stderr, "Failed to authorize [%s] at '%s'" % (msg, url)
data = e.read()
if data:
print >> sys.stderr, "Extra data: %s" % data
print >> sys.stderr, "Likely cause: incorrect API keys, id=%s" % kid
else:
print >> sys.stderr, "Unknown error fetching '%s'" % url
return None

def getInitialLink(self, fromDate, events_per_page):
url = "%s:%d/%s/events?per_page=%d" % (self.base_url, self.port, self.api_ver, events_per_page)
if (fromDate):
url += "&since=" + fromDate
return url

def getEventBatch(self, url):
return self.doGetRequest(url, self.authToken)

def doGetRequest(self, url, token):
req = urllib2.Request(url)
req.add_header("Authorization", "Bearer " + token)
try:
fh = urllib2.urlopen(req)
return (fh.read(), False)
except IOError, e:
authError = False
if hasattr(e, 'reason'):
print >> sys.stderr, "Failed to connect [%s] to '%s'" % (e.reason, url)
elif hasattr(e, 'code'):
msg = self.getHttpStatus(e.code)
print >> sys.stderr, "Failed to fetch events [%s] from '%s'" % (msg, url)
if (e.code == 401):
authError = True
else:
print >> sys.stderr, "Unknown error fetching '%s'" % url
return (None, authError)

def doPutRequest(self, url, token, putData):
opener = urllib2.build_opener(urllib2.HTTPHandler)
req = urllib2.Request(url, data=putData)
req.add_header("Authorization", "Bearer " + token)
req.add_header("Content-Type", "application/json")
req.get_method = lambda: 'PUT'
try:
fh = opener.open(req)
return (fh.read(), False)
except IOError, e:
authError = False
if hasattr(e, 'reason'):
print >> sys.stderr, "Failed to connect [%s] to '%s'" % (e.reason, url)
if hasattr(e, 'code'):
msg = self.getHttpStatus(e.code)
print >> sys.stderr, "Failed to make request: [%s] from '%s'" % (msg, url)
if (e.code == 401):
authError = True
if (not hasattr(e, 'reason')) and (not hasattr(e, 'code')):
print >> sys.stderr, "Unknown error fetching '%s'" % url
return (None, authError)

def doPostRequest(self, url, token, putData):
opener = urllib2.build_opener(urllib2.HTTPHandler)
req = urllib2.Request(url, data=putData)
req.add_header("Authorization", "Bearer " + token)
req.add_header("Content-Type", "application/json")
try:
fh = opener.open(req)
return (fh.read(), False)
except IOError, e:
authError = False
if hasattr(e, 'reason'):
print >> sys.stderr, "Failed to connect [%s] to '%s'" % (e.reason, url)
if hasattr(e, 'code'):
msg = self.getHttpStatus(e.code)
print >> sys.stderr, "Failed to make request: [%s] from '%s'" % (msg, url)
if (e.code == 401):
authError = True
if (not hasattr(e, 'reason')) and (not hasattr(e, 'code')):
print >> sys.stderr, "Unknown error fetching '%s'" % url
return (None, authError)

def authenticateClient(self):
url = "%s:%d/%s" % (self.base_url, self.port, self.auth_url)
self.token = None
response = self.getAuthToken(url, self.auth_args, self.key_id, self.secret)
if (response):
authRespObj = json.loads(response)
if ('access_token' in authRespObj):
self.authToken = authRespObj['access_token']
if ('expires_in' in authRespObj):
self.expires = authRespObj['expires_in']
# dumpToken(token,expires)
return self.authToken

def getServerList(self):
url = "%s:%d/%s/servers" % (self.base_url, self.port, self.api_ver)
(data, authError) = self.doGetRequest(url, self.authToken)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)

def getServerGroupList(self):
url = "%s:%d/%s/groups" % (self.base_url, self.port, self.api_ver)
(data, authError) = self.doGetRequest(url, self.authToken)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)

def getFirewallPolicyList(self):
url = "%s:%d/%s/firewall_policies/" % (self.base_url, self.port, self.api_ver)
(data, authError) = self.doGetRequest(url, self.authToken)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)

def getFirewallPolicyDetails(self, policyID):
url = "%s:%d/%s/firewall_policies/%s" % (self.base_url, self.port, self.api_ver, policyID)
(data, authError) = self.doGetRequest(url, self.authToken)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)

def moveServerToGroup(self, serverID, groupID):
url = "%s:%d/%s/servers/%s" % (self.base_url, self.port, self.api_ver, serverID)
reqData = {"server": {"group_id": groupID}}
jsonData = json.dumps(reqData)
# print "move: %s" % jsonData
(data, authError) = self.doPutRequest(url, self.authToken, jsonData)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)

def createServerGroup(self, groupName, linuxFirewallPolicy, windowsFirewallPolicy):
url = "%s:%d/%s/groups" % (self.base_url, self.port, self.api_ver)
groupData = {"name": groupName, "policy_ids": [], "tag": None}
groupData["linux_firewall_policy_id"] = linuxFirewallPolicy
groupData["windows_firewall_policy_id"] = windowsFirewallPolicy
reqData = {"group": groupData}
jsonData = json.dumps(reqData)
(data, authError) = self.doPostRequest(url, self.authToken, jsonData)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)

def createFirewallPolicy(self, policyData):
url = "%s:%d/%s/firewall_policies" % (self.base_url, self.port, self.api_ver)
jsonData = json.dumps(policyData)
# print jsonData # for debugging
(data, authError) = self.doPostRequest(url, self.authToken, jsonData)
if (data):
return (json.loads(data), authError)
else:
return (None, authError)
Binary file added cpapi.pyc
Binary file not shown.
26 changes: 26 additions & 0 deletions cpsyslog.py
@@ -0,0 +1,26 @@
#!/usr/bin/python

import syslog

# allows us to use the same names as in remote_syslog.py, but translated to values used by standard syslog module

FACILITY = {
'kern': syslog.LOG_KERN, 'user': syslog.LOG_USER, 'mail': syslog.LOG_MAIL, 'daemon': syslog.LOG_DAEMON,
'auth': syslog.LOG_AUTH, 'syslog': syslog.LOG_SYSLOG, 'lpr': syslog.LOG_LPR, 'news': syslog.LOG_NEWS,
'uucp': syslog.LOG_UUCP, 'cron': syslog.LOG_CRON, 'authpriv': syslog.LOG_AUTH,
'local0': syslog.LOG_LOCAL0, 'local1': syslog.LOG_LOCAL1, 'local2': syslog.LOG_LOCAL2, 'local3': syslog.LOG_LOCAL3,
'local4': syslog.LOG_LOCAL4, 'local5': syslog.LOG_LOCAL5, 'local6': syslog.LOG_LOCAL6, 'local7': syslog.LOG_LOCAL7,
}

LEVEL = {
'emerg': syslog.LOG_EMERG, 'alert': syslog.LOG_ALERT, 'crit': syslog.LOG_CRIT, 'err': syslog.LOG_ERR,
'warning': syslog.LOG_WARNING, 'notice': syslog.LOG_NOTICE, 'info': syslog.LOG_INFO, 'debug': syslog.LOG_DEBUG
}

# From http://docs.python.org/2.6/library/syslog.html
# Priority levels (high to low):
# LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG.
# Facilities:
# LOG_KERN, LOG_USER, LOG_MAIL, LOG_DAEMON, LOG_AUTH, LOG_LPR, LOG_NEWS, LOG_UUCP, LOG_CRON and LOG_LOCAL0 to LOG_LOCAL7.
# Log options:
# LOG_PID, LOG_CONS, LOG_NDELAY, LOG_NOWAIT and LOG_PERROR if defined in <syslog.h>.

0 comments on commit d3e2f41

Please sign in to comment.