Skip to content

Commit 24585cc

Browse files
committed
code organization
1 parent 9a13c4d commit 24585cc

9 files changed

+694
-591
lines changed

osquery/__init__.py

+27-591
Large diffs are not rendered by default.

osquery/config_plugin.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""LICENSE file in the root directory of this source tree. An additional grant
2+
of patent rights can be found in the PATENTS file in the same directory.
3+
"""
4+
5+
# pylint: disable=no-self-use
6+
7+
from __future__ import absolute_import
8+
from __future__ import division
9+
from __future__ import print_function
10+
from __future__ import unicode_literals
11+
12+
from abc import ABCMeta, abstractmethod
13+
14+
from osquery.extensions.ttypes import ExtensionResponse, ExtensionStatus
15+
from osquery.plugin import BasePlugin
16+
17+
class ConfigPlugin(BasePlugin):
18+
"""All config plugins should inherit from ConfigPlugin"""
19+
__metaclass__ = ABCMeta
20+
21+
def call(self, context):
22+
"""Internal routing for this plugin type.
23+
24+
Do not override this method.
25+
"""
26+
if "action" in context:
27+
if context["action"] == "genConfig":
28+
return ExtensionResponse(status=ExtensionStatus(code=0,
29+
message="OK",),
30+
response=self.content(),)
31+
32+
message = "Not a valid config plugin action"
33+
return ExtensionResponse(status=ExtensionStatus(code=1,
34+
message=message,),
35+
response=[],)
36+
37+
def registry_name(self):
38+
"""The name of the registry type for config plugins.
39+
40+
Do not override this method.
41+
"""
42+
return "config"
43+
44+
@abstractmethod
45+
def content(self):
46+
"""The implementation of your config plugin.
47+
48+
This should return a dictionary of the following format:
49+
50+
[
51+
{
52+
"source_name_1": serialized_json_config_string_1,
53+
"source_name_2": serialized_json_config_string_2,
54+
}
55+
]
56+
57+
Consider the following full example of the content method:
58+
59+
@osquery.register_plugin
60+
class TestConfigPlugin(osquery.ConfigPlugin):
61+
def name(self):
62+
return "test_config"
63+
64+
def content(self):
65+
return [
66+
{
67+
"source_one": json.dumps({
68+
"schedule": {
69+
"time_1": {
70+
"query": "select * from time",
71+
"interval": 1,
72+
},
73+
},
74+
}),
75+
"source_two": json.dumps({
76+
"schedule": {
77+
"time_2": {
78+
"query": "select * from time",
79+
"interval": 2,
80+
},
81+
},
82+
}),
83+
}
84+
]
85+
86+
This must be implemented by your plugin.
87+
"""
88+
raise NotImplementedError

osquery/extension_client.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""LICENSE file in the root directory of this source tree. An additional grant
2+
of patent rights can be found in the PATENTS file in the same directory.
3+
"""
4+
5+
from __future__ import absolute_import
6+
from __future__ import division
7+
from __future__ import print_function
8+
from __future__ import unicode_literals
9+
10+
try:
11+
from thrift.transport import TSocket
12+
from thrift.transport import TTransport
13+
from thrift.protocol import TBinaryProtocol
14+
except ImportError:
15+
print("Cannot import thrift: pip install thrift")
16+
exit(1)
17+
18+
from osquery.extensions.ExtensionManager import Client
19+
20+
DEFAULT_SOCKET_PATH = "/var/osquery/osquery.em"
21+
"""The default path for osqueryd sockets"""
22+
23+
class ExtensionClient(object):
24+
"""A client for connecting to an existing extension manager socket"""
25+
26+
_transport = None
27+
28+
def __init__(self, path=DEFAULT_SOCKET_PATH, uuid=None):
29+
"""
30+
Keyword arguments:
31+
path -- the path of the extension socket to connect to
32+
uuid -- the additional UUID to use when constructing the socket path
33+
"""
34+
self.path = path
35+
if uuid:
36+
self.path += ".%s" % str(uuid)
37+
transport = TSocket.TSocket(unix_socket=self.path)
38+
transport = TTransport.TBufferedTransport(transport)
39+
self.protocol = TBinaryProtocol.TBinaryProtocol(transport)
40+
self._transport = transport
41+
42+
def close(self):
43+
"""Close the extension client connection"""
44+
if self._transport:
45+
self._transport.close()
46+
47+
def open(self):
48+
"""Attempt to open the UNIX domain socket"""
49+
self._transport.open()
50+
51+
def extension_manager_client(self):
52+
"""Return an extension manager (osquery core) client."""
53+
return Client(self.protocol)
54+
55+
def extension_client(self):
56+
"""Return an extension (osquery extension) client."""
57+
return Client(self.protocol)

osquery/extension_manager.py

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""LICENSE file in the root directory of this source tree. An additional grant
2+
of patent rights can be found in the PATENTS file in the same directory.
3+
"""
4+
5+
from __future__ import absolute_import
6+
from __future__ import division
7+
from __future__ import print_function
8+
from __future__ import unicode_literals
9+
10+
from osquery.extensions.Extension import Iface
11+
from osquery.extensions.ttypes import ExtensionResponse, ExtensionStatus
12+
from osquery.singleton import Singleton
13+
14+
class ExtensionManager(Singleton, Iface):
15+
"""The thrift server for handling extension requests
16+
17+
An extension's manager is responsible for maintaining the state of
18+
registered plugins, broadcasting the registry of those plugins to the
19+
core's extension manager and fielding requests that come in on the
20+
extension's socket.
21+
"""
22+
_plugins = {}
23+
_registry = {}
24+
25+
uuid = None
26+
27+
def add_plugin(self, plugin):
28+
"""Register a plugin with the extension manager. In order for the
29+
extension manager to broadcast a plugin, it must be added using this
30+
interface.
31+
32+
Keyword arguments:
33+
plugin -- the plugin class to register
34+
"""
35+
36+
# First, we create an instance of the plugin. All plugins are
37+
# singletons, so this instance will be long-lived.
38+
obj = plugin()
39+
40+
41+
# When the extension manager broadcasts it's registry to core's
42+
# extension manager, the data structure should follow a specific
43+
# format. Whenever we add a plugin, we need to update the internal
44+
# _registry instance variable, which will be sent to core's extension
45+
# manager once the extension has been started
46+
if obj.registry_name() not in self._registry:
47+
self._registry[obj.registry_name()] = {}
48+
if obj.name() not in self._registry[obj.registry_name()]:
49+
self._registry[obj.registry_name()][obj.name()] = obj.routes()
50+
51+
# The extension manager needs a way to route calls to the appropriate
52+
# implementation class. We maintain references to the plugin's
53+
# singleton instantiation in the _plugins instance variable. The
54+
# _plugins member has the same general structure as _registry, but
55+
# instead of pointing to the plugin's routes, it points to the plugin
56+
# implementation object
57+
if obj.registry_name() not in self._plugins:
58+
self._plugins[obj.registry_name()] = {}
59+
if obj.name() not in self._plugins[obj.registry_name()]:
60+
self._plugins[obj.registry_name()][obj.name()] = obj
61+
62+
def registry(self):
63+
"""Accessor for the internal _registry member variable"""
64+
return self._registry
65+
66+
def ping(self):
67+
"""Lightweight health verification
68+
69+
The core osquery extension manager will periodically "ping" each
70+
extension that has connected to it to ensure that the extension is
71+
still active and can field requests, if necessary.
72+
"""
73+
return ExtensionStatus(code=0, message="OK")
74+
75+
def call(self, registry, item, request):
76+
"""The entry-point for plugin requests
77+
78+
When a plugin is accessed from another process, osquery core's
79+
extension manager will send a thrift request to the implementing
80+
extension manager's call method.
81+
82+
Arguments:
83+
registry -- a string representing what registry is being accessed.
84+
for config plugins this is "config", for table plugins this is
85+
"table", etc.
86+
item -- the registry item that is being requested. this is the "name"
87+
of your plugin. for example, this would be the exact name of the
88+
SQL table, if the plugin was a table plugin.
89+
"""
90+
91+
# this API only support plugins of the following types:
92+
# - table
93+
# - config
94+
# - logger
95+
if registry not in ["table", "config", "logger"]:
96+
message = "A registry of an unknown type was called: %s" % registry
97+
return ExtensionResponse(status=ExtensionStatus(code=1, message=message,),
98+
response=[],)
99+
100+
try:
101+
return self._plugins[registry][item].call(request)
102+
except KeyError:
103+
message = "Extension registry does not contain requested plugin"
104+
return ExtensionResponse(status=ExtensionStatus(code=1, message=message,),
105+
response=[],)

osquery/logger_plugin.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""LICENSE file in the root directory of this source tree. An additional grant
2+
of patent rights can be found in the PATENTS file in the same directory.
3+
"""
4+
5+
# pylint: disable=no-self-use
6+
# pylint: disable=unused-argument
7+
8+
from __future__ import absolute_import
9+
from __future__ import division
10+
from __future__ import print_function
11+
from __future__ import unicode_literals
12+
13+
from abc import ABCMeta, abstractmethod
14+
15+
from osquery.extensions.ttypes import ExtensionResponse, ExtensionStatus
16+
from osquery.plugin import BasePlugin
17+
18+
class LoggerPlugin(BasePlugin):
19+
"""All logger plugins should inherit from LoggerPlugin"""
20+
__metaclass__ = ABCMeta
21+
22+
_use_glog_message = "Use Glog for status logging"
23+
_invalid_action_message = "Not a valid logger plugin action"
24+
25+
def call(self, context):
26+
"""Internal routing for this plugin type.
27+
28+
Do not override this method.
29+
"""
30+
if "string" in context:
31+
return ExtensionResponse(status=self.log_string(context["string"]),
32+
response=[],)
33+
elif "snapshot" in context:
34+
return ExtensionResponse(
35+
status=self.log_snapshot(context["snapshot"]),
36+
response=[],)
37+
elif "health" in context:
38+
return ExtensionResponse(status=self.log_health(context["health"]),
39+
response=[],)
40+
elif "init" in context:
41+
return ExtensionResponse(
42+
status=ExtensionStatus(code=1,
43+
message=self._use_glog_message,),
44+
response=[],)
45+
elif "status" in context:
46+
return ExtensionResponse(
47+
status=ExtensionStatus(code=1,
48+
message=self._use_glog_message,),
49+
response=[],)
50+
else:
51+
return ExtensionResponse(
52+
status=ExtensionStatus(code=1,
53+
message=self._invalid_action_message,),
54+
response=[],)
55+
56+
def registry_name(self):
57+
"""The name of the registry type for logger plugins.
58+
59+
Do not override this method."""
60+
return "logger"
61+
62+
@abstractmethod
63+
def log_string(self, value):
64+
"""The implementation of your logger plugin.
65+
66+
This must be implemented by your plugin.
67+
68+
This must return an ExtensionStatus
69+
70+
Arguments:
71+
value -- the string to log
72+
"""
73+
raise NotImplementedError
74+
75+
def log_health(self, value):
76+
"""If you'd like the log health statistics about osquery's performance,
77+
override this method in your logger plugin.
78+
79+
By default, this action is a noop.
80+
81+
This must return an ExtensionStatus
82+
"""
83+
return ExtensionStatus(code=0, message="OK",)
84+
85+
def log_snapshot(self, value):
86+
"""If you'd like to log snapshot queries in a special way, override
87+
this method.
88+
89+
By default, this action is just hands off the string to log_string.
90+
91+
This must return an ExtensionStatus
92+
"""
93+
return self.log_string(value)

0 commit comments

Comments
 (0)