Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MSSP functionality to Hosts and User Management samples, fix CSPM policies sample #575

Merged
merged 5 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/bandit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install bandit
python -m pip install bandit==1.7.2
pip install -r requirements.txt
- name: Analyze package with bandit
run: |
Expand Down
9 changes: 5 additions & 4 deletions samples/README.md

Large diffs are not rendered by default.

124 changes: 84 additions & 40 deletions samples/cspm_registration/get_cspm_policies.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""CrowdStrike Horizon - Retrieve CSPM Policies
r"""CrowdStrike Horizon - Retrieve CSPM Policies

___ ____ ____ ___ ____ ____ _ _ ____ __ __ __ ___ __ ____ ____
/ __)( __)(_ _) / __)/ ___)( _ \( \/ ) ( _ \ / \ ( ) ( )/ __)( )( __)/ ___)
( (_ \ ) _) )( ( (__ \___ \ ) __// \/ \ ) __/( O )/ (_/\ )(( (__ )( ) _) \___ \
\___/(____) (__) \___)(____/(__) \_)(_/ (__) \__/ \____/(__)\___)(__)(____)(____/

This example uses the CSPM Registration Class to output Horizon policies to CSV.

Expand Down Expand Up @@ -27,25 +32,25 @@
python3 get_cspm_policies.py -c azure -o ~/Documents/azure-policies.csv

"""
# ___ ____ ____ ___ ____ ____ _ _ ____ __ __ __ ___ __ ____ ____
# / __)( __)(_ _) / __)/ ___)( _ \( \/ ) ( _ \ / \ ( ) ( )/ __)( )( __)/ ___)
# ( (_ \ ) _) )( ( (__ \___ \ ) __// \/ \ ) __/( O )/ (_/\ )(( (__ )( ) _) \___ \
# \___/(____) (__) \___)(____/(__) \_)(_/ (__) \__/ \____/(__)\___)(__)(____)(____/
#
# pylint: disable=C0209
#
import argparse
import json
import csv
from json.decoder import JSONDecodeError
import os
import sys
import logging
from falconpy import CSPMRegistration
from argparse import ArgumentParser, RawTextHelpFormatter
from tabulate import tabulate
try:
from falconpy import CSPMRegistration
except ImportError as no_falconpy:
raise SystemExit(
"The crowdstrike-falconpy package must be installed to run this program."
) from no_falconpy


# Capture command line arguments
parser = argparse.ArgumentParser(
description="Gather API client_id and client_secret from arguments")
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-f", "--falcon_client_id",
help="Falcon Client ID", default=None, required=False)
parser.add_argument("-s", "--falcon_client_secret",
Expand Down Expand Up @@ -87,38 +92,36 @@ def format_json_data(json_data):
The goal of this function is to bring uniformity to the api
returned data so it can be reported in csv format.
"""
length = 0
headers = []
for pol in json_data:
if len(pol.keys()) > length:
length = len(pol.keys())
headers = [*pol]
list_dict = []
for pol in json_data:
policy = ""
for head in headers:
if head in pol.keys():
if head == headers[-1]:
str_line = "\"{}\": \"{}\"".format(
head, str(pol[head]).strip("\n").replace('"', ''))
else:
str_line = "\"{}\": \"{}\", ".format(
head, str(pol[head]).strip("\n").replace('"', ''))
else:
if head == headers[-1]:
str_line = "\"{}\": \"{}\"".format(head, "")
else:
str_line = "\"{}\": \"{}\", ".format(head, "")
policy += str_line
new_dict = "{{{}}}".format(policy)
try:
list_dict.append(json.loads(new_dict))
except JSONDecodeError:
# Throw out any decode errors
pass
checks = ["cloud_service", "cloud_asset_type_id", "cloud_asset_type", "nist_benchmark",
"cis_benchmark", "fql_policy", "policy_settings", "pci_benchmark",
"soc2_benchmark", "cloud_service_subtype"]
for data_row in json_data:
for check in checks:
if check not in data_row:
data_row[check] = ""
list_dict.append(data_row)

return list_dict


def chunk_long_description(desc, col_width) -> str:
"""Chunks a long string by delimiting with CR based upon column length."""
desc_chunks = []
chunk = ""
for word in desc.split():
new_chunk = f"{chunk} {word.strip()}"
if len(new_chunk) >= col_width:
desc_chunks.append(new_chunk)
chunk = ""
else:
chunk = new_chunk

delim = "\n"

return delim.join(desc_chunks)


# Retrieve our list of policy settings
policies = falcon.get_policy_settings(cloud_platform=cloud)['body']['resources']
# Call format function on the returned api data
Expand All @@ -132,4 +135,45 @@ def format_json_data(json_data):
dict_writer.writeheader()
dict_writer.writerows(return_data)
else:
print(return_data)
results = []
for row in return_data:
row.pop("cloud_service", None)
row.pop("cloud_asset_type_id", None)
row.pop("cloud_asset_type", None)
row.pop("fql_policy", None)
row.pop("is_remediable", None)
name_val = f"[{row['policy_id']}] {chunk_long_description(row['name'], 40)}"
name_val = f"{name_val}\n{row['policy_type']}"
if row["cloud_service_subtype"]:
name_val = f"{name_val} // {row['cloud_service_subtype']}"
name_val = f"{name_val}\n{row['default_severity'].title()} severity"
name_val = f"{name_val}\nCloud provider: {row['cloud_provider'].upper()}"
row["name"] = name_val
row.pop("policy_settings", None)
row.pop("policy_id", None)
row.pop("policy_type", None)
row.pop("cloud_provider", None)
row.pop("default_severity", None)
row.pop("policy_timestamp", None)
row.pop("cloud_service_subtype", None)
benchmarks = ["cis", "pci", "nist", "soc2"]
benchmark_list = []
for benchmark_name in benchmarks:
for benchmark in row[f"{benchmark_name}_benchmark"]:
bench_value = [f"[{benchmark['id']}]",
f"{benchmark['benchmark_short']}",
f"({benchmark['recommendation_number']})"
]
benchmark_list.append(" ".join(bench_value))
row[f"{benchmark_name}_benchmark"] = "\n".join(benchmark_list)

results.append(row)

headers = {
"name": "Name",
"cis_benchmark": "CIS",
"pci_benchmark": "PCI",
"nist_benchmark": "NIST",
"soc2_benchmark": "SOC2"
}
print(tabulate(tabular_data=results, tablefmt="fancy_grid", headers=headers))
49 changes: 33 additions & 16 deletions samples/hosts/sensor_versions_by_hostname.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ def parse_command_line() -> object:
help='CrowdStrike Falcon API key secret',
required=True
)
parser.add_argument(
'-m',
'--mssp',
help='Child CID to access (MSSP only)',
required=False
)
parser.add_argument(
'-b',
'--base_url',
Expand All @@ -42,44 +48,52 @@ def parse_command_line() -> object:
def device_list(off: int, limit: int, sort: str):
"""Return a list of all devices for the CID, paginating when necessary."""
result = falcon.query_devices_by_filter(limit=limit, offset=off, sort=sort)
new_offset = result["body"]["meta"]["pagination"]["offset"]
total = result["body"]["meta"]["pagination"]["total"]
returned_device_list = result["body"]["resources"]
return new_offset, total, returned_device_list
new_offset = 0
total = 0
returned_device_list = []
if result["status_code"] == 200:
new_offset = result["body"]["meta"]["pagination"]["offset"]
total = result["body"]["meta"]["pagination"]["total"]
returned_device_list = result["body"]["resources"]

return new_offset, total, returned_device_list

def device_detail(aids: list):
"""Return the device_id and agent_version for a list of AIDs provided."""
result = falcon.get_device_details(ids=aids)
device_details = []
# return just the aid and agent version
for device in result["body"]["resources"]:
res = {}
res["hostname"] = device.get("hostname", None)
res["agent_version"] = device.get("agent_version", None)
device_details.append(res)
if result["status_code"] == 200:
# return just the aid and agent version
for device in result["body"]["resources"]:
res = {}
res["hostname"] = device.get("hostname", None)
res["agent_version"] = device.get("agent_version", None)
device_details.append(res)
return device_details


args = parse_command_line()

BASE = "auto"
if args.base_url:
BASE = args.base_url
else:
BASE = "us1"

SORT = "hostname.asc"
if args.reverse:
SORT = "hostname.desc"
else:
SORT = "hostname.asc"

CHILD = None
if args.mssp:
CHILD = args.mssp

falcon = Hosts(client_id=args.client_id,
client_secret=args.client_secret,
base_url=BASE
base_url=BASE,
member_cid=CHILD
)

OFFSET = 0 # Start at the beginning
DISPLAYED = 0 # This is just so we can show a running count
DISPLAYED = 0 # Running count
TOTAL = 1 # Assume there is at least one
LIMIT = 500 # Quick limit to prove pagination
while OFFSET < TOTAL:
Expand All @@ -88,3 +102,6 @@ def device_detail(aids: list):
for detail in details:
DISPLAYED += 1
print(f"{DISPLAYED}: {detail['hostname']} is on version {detail['agent_version']}")

if not DISPLAYED:
print("No results returned.")
37 changes: 22 additions & 15 deletions samples/hosts/stale_sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ def parse_command_line() -> object:
help='CrowdStrike Falcon API key secret',
required=True
)
parser.add_argument(
'-m',
'--mssp',
help='Child CID to access (MSSP only)',
required=False
)
parser.add_argument(
'-b',
'--base_url',
Expand Down Expand Up @@ -81,11 +87,11 @@ def parse_command_line() -> object:
return parser.parse_args()


def connect_api(key: str, secret: str, base_url: str) -> Hosts:
def connect_api(key: str, secret: str, base_url: str, child_cid: str = None) -> Hosts:
"""
Connects to the API and returns an instance of the Hosts Service Class.
"""
return Hosts(client_id=key, client_secret=secret, base_url=base_url)
return Hosts(client_id=key, client_secret=secret, base_url=base_url, member_cid=child_cid)


def get_host_details(id_list: list) -> list:
Expand Down Expand Up @@ -150,43 +156,44 @@ def hide_hosts(id_list: list) -> dict:

# Parse our command line
args = parse_command_line()

# Default SORT to ASC if not present
if not args.reverse:
SORT = False
else:
SORT = False
if args.reverse:
SORT = bool(args.reverse)

if not args.base_url:
BASE = "us1"
else:
BASE = "auto"
if args.base_url:
BASE = args.base_url

CHILD = None
if args.mssp:
CHILD = args.mssp

# Credentials
api_client_id = args.client_id
api_client_secret = args.client_secret
if not api_client_id and not api_client_secret:
raise SystemExit("Invalid API credentials provided.")

# Set our stale date to 120 days if not present
if not args.days:
STALE_DAYS = 120
else:
STALE_DAYS = 120
if args.days:
try:
STALE_DAYS = int(args.days)
except ValueError as bad_day_value:
raise SystemExit("Invalid value specified for days. Integer required.") from bad_day_value

# Do not hide hosts if it is not requested
if not args.remove:
HIDE = False
else:
HIDE = False
if args.remove:
HIDE = bool(args.remove)

# Calculate our stale date filter
STALE_DATE = calc_stale_date(STALE_DAYS)

# Connect to the API
falcon = connect_api(api_client_id, api_client_secret, BASE)
falcon = connect_api(api_client_id, api_client_secret, BASE, CHILD)

# List to hold our identified hosts
stale = []
Expand Down
8 changes: 8 additions & 0 deletions samples/user_management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The following arguments are accepted at run time.
| `-c` COMMAND | `--command` COMMAND | Action to perform<ul><li>list</li><li>add</li><li>remove</li><li>update</li><li>getroles</li></ul>Defaults to __list__ |
| `-k` FALCON_CLIENT_ID | `--falcon_client_id` FALCON_CLIENT_ID | Falcon Client ID |
| `-s` FALCON_CLIENT_SECRET | `--falcon_client_secret` FALCON_CLIENT_SECRET | Falcon Client Secret |
| `-m` CHILD_CID | `--mssp` CHILD_CID | CID for the child instance you wish to access. (MSSP scenarios only) |
| `-o` SORT | `--sort` SORT | Field to sort by, one of:<ul><li>firstName</li><li>lastName</li><li>roles</li><li>uid</li><li>uuid</li></ul>Defaults to __lastName__ (_asc_) |
| `-r` | `--reverse` | Reverse the sort order |
| `-n` | `--no_color` | Disable color output in result displays |
Expand All @@ -72,6 +73,13 @@ The default command is _list_ which requires no additional input.
python3 bulk_user.py -k CLIENT_ID -s CLIENT_SECRET
```

#### MSSP access
To access child user data, you will need to provide the child CID when you execute the program.

```shell
python3 bulk_user.py -k CLIENT_ID -s CLIENT_SECRET -m CHILD_CID
```

#### Sorting results
Results may be sorted by column in ascending or descending order using the `-o` and `-r` arguments.

Expand Down
Loading