!!! warning "Deprecated" Version 4 of the LimaCharlie Python SDK is deprecated and will be removed in a future release. New code should use the Python SDK (v5). This page is kept for users maintaining existing v4 integrations. Both v4 and v5 target the same LimaCharlie REST API, so the underlying capabilities available to your code are unchanged.
The v4 Python SDK is a thin abstraction over the LimaCharlie REST API,
centered on the Manager object. The REST API supports more functionality
than the SDK; if a REST endpoint you need is not exposed in v4, prefer
migrating to v5 rather than extending v4.
- Source (v4 branch): https://github.com/refractionPOINT/python-limacharlie/tree/v4
- REST API: https://api.limacharlie.io
!!! warning "v4 is deprecated"
pip install limacharlie now installs v5. To stay on v4 (not
recommended), pin to the v4 release line:
```bash
pip install "limacharlie<5"
```
- Python 3.x
- pip
Core dependencies (installed automatically):
requestspasslibpyyamltabulatetermcolorpygmentsrichcryptography>=44.0.1
The SDK and CLI support several ways of providing credentials.
The simplest path is to log in with an API key:
limacharlie loginYou will be prompted for an OID (Organization ID, in UUID format) and an
API key, both available from the REST API section of the web interface.
The login flow supports named environments — credentials are stored under a chosen name, with one set used as the default when no environment is selected.
To list available environments:
limacharlie useTo activate a named environment in the current shell session:
. <(limacharlie use dev-org)You can also provide a UID (User ID) at login to use a user-scoped API
key, which carries the full set of permissions assigned to that user (see
User Profile in the web interface).
LC_OID, LC_API_KEY, and LC_UID can replace the values stored at login.
Environment variables are used when no other credentials are explicitly
provided.
When using limacharlie login, credentials are stored in plain text at
~/.limacharlie:
# Top-level / default credentials
api_key: xxx
oid: xxx
# Optional, only required for user-scoped API keys
uid: xxx
# Named environments, selected via "limacharlie use <environment>"
env:
org-1:
api_key: xxx
oid: xxx
# uid: xxx
org-2:
api_key: xxx
oid: xxx
# uid: xxxIf you edit this file by hand, preserve the original ownership and 0600
permissions so that other users cannot read it. If storing plain-text
credentials on disk is unacceptable for your environment, use environment
variables instead.
A Docker image with the latest tool is published at https://hub.docker.com/r/refractionpoint/limacharlie:
docker run refractionpoint/limacharlie:latest whoami
# Or mount an existing local credentials file into the container
docker run -v "${HOME}/.limacharlie:/root/.limacharlie:ro" \
refractionpoint/limacharlie:latest whoamiThe entry point for the SDK is the Manager object. It holds credentials
and is bound to a specific Organization.
You can authenticate Manager by passing an oid (and optionally a uid)
together with either a secret_api_key or a jwt. Alternatively, pass a
named environment from limacharlie login. If no credentials are
provided, Manager falls back to the default environment.
import limacharlie
YARA_SIG = 'https://raw.githubusercontent.com/Yara-Rules/rules/master/Malicious_Documents/Maldoc_PDF.yar'
# Create an instance of the SDK.
man = limacharlie.Manager()
# Manager.sensors() is a generator that paginates internally.
all_sensors = list(man.sensors())
# Select the first sensor.
sensor = all_sensors[0]
# Tag this sensor for 10 minutes.
sensor.tag('suspicious', ttl=60 * 10)
# Send a task to the sensor (unidirectional, no response collected).
sensor.task('os_processes')
# Send a YARA scan to the sensor for processes named "evil.exe".
sensor.task('yara_scan -e *evil.exe ' + YARA_SIG)The general-purpose component for organization-level operations: querying sensors, creating and removing Outputs, and so on.
Firehose listens on a local port for LimaCharlie data. Internally it
creates a Syslog Output on LimaCharlie pointing at itself, and removes that
Output on shutdown. Incoming data is added to firehose.queue (a standard
queue.Queue) as it arrives.
It is a basic building block for automation against LimaCharlie.
Spout plays a similar role to Firehose, but does not require an open
listening port. Instead it streams data over HTTPS from
stream.limacharlie.io, which makes it work behind NATs and proxies.
A Spout is created automatically when Manager is instantiated with
is_interactive=True and an inv_id, in order to provide real-time
feedback from sensor tasking.
The object returned by manager.sensor(sensor_id).
It exposes task, hostname, tag, untag, getTags, and related
functions, and is the main way to interact with a specific sensor.
task sends a one-way task to a sensor; the response (if any) is not
collected. To interact with a sensor in real time, instantiate Manager
with is_interactive=True and use either request (returns a
FutureResults object) or simpleRequest (blocks until the response is
available).
Artifacts uploads
Artifact Collection
items to LimaCharlie without going through a sensor.
Payloads manages the executable
payloads made
available to sensors.
Replay runs Replay jobs to
re-evaluate
D&R rules over historical data.
Search performs an IOC search across multiple organizations.
SpotCheck (also called Fleet Check) performs an active search — querying
sensors directly rather than indexed history — for various IOCs across an
organization's sensors.
Configs retrieves an organization's configuration as a config file, or
applies a config file to an organization. This is the foundation of the
Infrastructure-as-Code workflow in v4.
Webhook is a reference implementation for handling webhooks emitted by
LimaCharlie, including verification of the shared-secret signature.
Adapted from
samples/demo_manager.py.
import limacharlie
import getpass
import json
if __name__ == "__main__":
def debugPrint(msg):
print(msg)
man = limacharlie.Manager(
oid=input('Enter OID: '),
secret_api_key=getpass.getpass(prompt='Enter secret API key: '),
print_debug_fn=debugPrint,
)
# Manager.sensors() is a generator that paginates internally.
all_sensors = list(man.sensors())
print("Got %d sensors." % len(all_sensors))
print("First sensor %s has tags: %s" % (
all_sensors[0].sid,
all_sensors[0].getTags(),
))
for sensor in all_sensors:
if not sensor.isOnline():
print("Sensor %s is offline, next..." % sensor.sid)
continue
print("Sensor info: %s" % json.dumps(sensor.getInfo(), indent=2))
sensor.task('dir_list . *')
breakAdapted from
samples/demo_firehose.py.
import limacharlie
import getpass
import json
import signal
import sys
if __name__ == "__main__":
def signal_handler(sig, frame):
global fh
print('You pressed Ctrl+C!')
fh.shutdown()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
def debugPrint(msg):
print(msg)
man = limacharlie.Manager(
oid=input('Enter OID: '),
secret_api_key=getpass.getpass(prompt='Enter secret API key: '),
print_debug_fn=debugPrint,
)
fh = limacharlie.Firehose(
man,
input('Local Interface (e.g. 1.2.3.4:9424): '),
'event',
public_dest=input('Public Interface (or leave empty): ') or None,
name='firehose_test',
)
while True:
data = fh.queue.get()
print(json.dumps(data, indent=2) + "\n\n")Adapted from
samples/demo_spout.py.
import limacharlie
import getpass
import json
import signal
import sys
if __name__ == "__main__":
def signal_handler(sig, frame):
global sp
print('You pressed Ctrl+C!')
sp.shutdown()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
def debugPrint(msg):
print(msg)
man = limacharlie.Manager(
oid=input('Enter OID: '),
secret_api_key=getpass.getpass(prompt='Enter secret API key: '),
print_debug_fn=debugPrint,
)
sp = limacharlie.Spout(man, 'event')
while True:
data = sp.queue.get()
print(json.dumps(data, indent=2) + "\n\n")Adapted from
samples/demo_interactive_sensor.py.
import limacharlie
import getpass
import json
import uuid
if __name__ == "__main__":
def debugPrint(msg):
print(msg)
print("Starting in interactive mode.")
man = limacharlie.Manager(
oid=input('Enter OID: '),
secret_api_key=getpass.getpass(prompt='Enter secret API key: '),
print_debug_fn=None,
inv_id=str(uuid.uuid4()),
is_interactive=True,
)
# is_interactive=True enables Sensor.request(), which returns a
# FutureResults object you can poll for the sensor's response.
sensors = list(man.sensors())
print("Got %d sensors." % len(sensors))
for sensor in sensors:
print("Sensor info: %s" % sensor.getInfo())
print("Asking for autoruns...")
try:
future = sensor.request('os_autoruns')
except limacharlie.utils.LcApiException as e:
if 'host not connected' in str(e):
print("Offline, moving on...")
continue
raise
responses = future.getNewResponses(timeout=10)
if not responses:
print("Never got a response.")
else:
print("Received: %s" % json.dumps(responses, indent=2))
print("All done.")Sample config files are available in the v4 branch at
limacharlie/sample_configs/.
Many of the SDK objects also expose command-line interfaces.
python -m limacharlie.Firehose 1.2.3.4:9424 event \
-n firehose_test -t fh_test \
--oid c82e5c17-d519-4ef5-a4ac-caa4a95d31caListens on 1.2.3.4:9424 for incoming connections from LimaCharlie.
Receives only events from hosts tagged fh_test.
python -m limacharlie.Spout event \
--oid c82e5c17-d519-4ef5-a4ac-caa4a95d31caBehaves like Firehose, but instead of accepting an incoming connection it
streams data from stream.limacharlie.io over HTTPS. This means Spout
works through NATs and proxies and is more convenient for short-lived
ad-hoc output, though less reliable than a Firehose for very large volumes.
limacharlie configs fetch --oid c82e5c17-d519-4ef5-a4ac-c454a95d31ca
limacharlie configs push --dry-run --oid c82e5c17-d519-4ef5-a4ac-c454a95d31cafetch writes the organization's configuration to a config file (default
lc_conf.yaml) in YAML format.
push uploads the rules in the config file to the organization. The
--force flag also removes active rules that are not present in the config
file. --dry-run simulates the sync and prints the changes that would be
made.
--config selects an alternate config file. --api-key reads the API key
from a file on disk (or from STDIN if - is given).
These capabilities are also available directly through the
limacharlie.Configs object.
The sync covers the common configurable surfaces. Flags such as
--no-rules and --no-outputs exclude individual sections; see
limacharlie configs --help for the full list. The include directive
lets you compose multiple config files together, which is convenient for
managing large rule sets.
python -m limacharlie.SpotCheck \
--no-macos --no-linux --tags vip --file 'c:\\evil.exe'Performs an organization-wide check for specific indicators of compromise.
Available as the SpotCheck object or as a CLI module. Supports many IOC
types including file names, directories, registry keys, file hashes, and
YARA signatures.
For full usage:
python -m limacharlie.SpotCheck --helplimacharlie search --helpPerforms IOC searches across all locally configured organizations.
limacharlie artifacts upload --helpUploads Artifact Collection items directly to LimaCharlie from the CLI (no agent required).
limacharlie artifacts get_original --helpDownloads Artifact Collection items from LimaCharlie to the local filesystem.
limacharlie replay --helpRuns Replay jobs from the CLI.
limacharlie dr --helpManages D&R rules from the CLI.
limacharlie events --help
limacharlie detections --helpPrints events or detections matching the given parameters to STDOUT.
limacharlie sensors --selector '*'Prints basic sensor information for all sensors matching the selector.
limacharlie extension --helpPerforms actions against Extensions from the CLI.
limacharlie get-arl --helpPrints the data returned from the given ARL. Example:
limacharlie get-arl -a [github,Yara-Rules/rules/email]