diff --git a/samples/hosts/README.md b/samples/hosts/README.md index a1bea4c6e..eb1cebc7d 100644 --- a/samples/hosts/README.md +++ b/samples/hosts/README.md @@ -746,6 +746,12 @@ This variation will retrieve a list of hosts that haven't checked in to CrowdStr python3 stale_sensors.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d 30 -t testtag ``` +This variation leverages a regular expression to match the host "SDKDEMO3". + +```shell +python3 stale_sensors.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d 30 -p "^SDK.*3$" +``` + You can reverse the list sort with the `-r` or `--reverse` argument. ```shell @@ -762,7 +768,8 @@ Command-line help is available via the `-h` argument. ```shell % python3 stale_sensors.py -h -usage: stale_sensors.py [-h] -k CLIENT_ID -s CLIENT_SECRET [-m MSSP] [-g] [-d DAYS] [-r] [-x] [-t TAG] +usage: stale_sensors.py [-h] -k CLIENT_ID -s CLIENT_SECRET [-m MSSP] [-g] [-d DAYS] [-r] [-x] [-t TAG] [-c] [-o OUTPUT_FILE] [-q] + [-f {windows,mac,linux,k8s}] [-p HOSTFILTER] CrowdStrike Unattended Stale Sensor Environment Detector. @@ -786,6 +793,10 @@ results for the US-GOV-1 region, pass the '-g' argument. - ray.heffer@crowdstrike.com; 03.29.22 - Added new argument for Grouping Tags (--grouping, -g) - @morcef, jshcodes@CrowdStrike; 06.05.22 - More reasonable date calcs, Linting, Easier arg parsing Easier base_url handling, renamed grouping_tag to tag +- jshcodes@Crowdstrike; 11.02.22 - Added CSV output options and cleaner date outputs. +- nmills@forbarr; 22.05.24 - Fixed deprecation warning on date function, + Added new arg to accept hostname pattern + Batch the call to hide_hosts to avoid API error optional arguments: -h, --help show this help message and exit @@ -799,6 +810,14 @@ optional arguments: -r, --reverse Reverse sort (defaults to ASC) -x, --remove Remove hosts identified as stale -t TAG, --tag TAG Falcon Grouping Tag name for the hosts + -c, --csv Export results to CSV + -o OUTPUT_FILE, --output_file OUTPUT_FILE + File to output CSV results to. Ignored when "-c" is not specified. + -q, --quotes Quote non-numeric fields in CSV output. + -f {windows,mac,linux,k8s}, --filter-by-os {windows,mac,linux,k8s} + OS filter (windows, macos, linux) + -p HOSTFILTER, --host-pattern HOSTFILTER + filter hostnames by regex ``` ### Example source code diff --git a/samples/hosts/stale_sensors.py b/samples/hosts/stale_sensors.py index be9dca774..1dabf7531 100644 --- a/samples/hosts/stale_sensors.py +++ b/samples/hosts/stale_sensors.py @@ -21,8 +21,12 @@ - @morcef, jshcodes@CrowdStrike; 06.05.22 - More reasonable date calcs, Linting, Easier arg parsing Easier base_url handling, renamed grouping_tag to tag - jshcodes@Crowdstrike; 11.02.22 - Added CSV output options and cleaner date outputs. +- nmills@forbarr; 22.05.24 - Fixed deprecation warning on date function, + Added new arg to accept hostname pattern + Batch the call to hide_hosts to avoid API error """ import csv +import re from argparse import ArgumentParser, RawTextHelpFormatter from datetime import datetime, timedelta, timezone from dateutil import parser as dparser @@ -35,7 +39,6 @@ "Please execute `python3 -m pip install crowdstrike-falconpy` and try again." ) from no_falconpy - def parse_command_line() -> object: """Parse command-line arguments and return them back as an ArgumentParser object.""" parser = ArgumentParser( @@ -74,7 +77,8 @@ def parse_command_line() -> object: '-d', '--days', help='Number of days since a host was seen before it is considered stale', - required=False + required=False, + default=10 ) parser.add_argument( '-r', @@ -130,9 +134,15 @@ def parse_command_line() -> object: required=False, dest="osfilter" ) + parser.add_argument( + "-p", "--host-pattern", + help="filter hostnames by regex", + default=r".*", + required=False, + dest="hostfilter" + ) return parser.parse_args() - def connect_api(key: str, secret: str, base_url: str, child_cid: str = None) -> Hosts: """Connect to the API and return an instance of the Hosts Service Class.""" return Hosts(client_id=key, client_secret=secret, base_url=base_url, member_cid=child_cid) @@ -156,6 +166,10 @@ def get_hosts(date_filter: str, tag_filter: str, os_filter: str) -> list: if os_filter == "K8s": os_filter = "K8S" filter_string = f"{filter_string} + platform_name:'{os_filter}'" + x = falcon.query_devices_by_filter_scroll( + limit=5000, + filter=filter_string + )["body"]["resources"] return falcon.query_devices_by_filter_scroll( limit=5000, filter=filter_string @@ -164,7 +178,7 @@ def get_hosts(date_filter: str, tag_filter: str, os_filter: str) -> list: def calc_stale_date(num_days: int) -> str: """Calculate the 'stale' datetime based upon the number of days provided by the user.""" - today = datetime.utcnow() + today = datetime.now(timezone.utc) return str(today - timedelta(days=num_days)).replace(" ", "T") @@ -212,14 +226,15 @@ def hide_hosts(id_list: list) -> dict: # List to hold our identified hosts stale = [] # For each stale host identified -try: - for host in get_host_details(get_hosts(STALE_DATE, args.tag, args.osfilter)): - # Retrieve host detail - stale = parse_host_detail(host, stale) -except KeyError as api_error: - raise SystemExit( - "Unable to communicate with CrowdStrike API, check credentials and try again." - ) from api_error +pattern = args.hostfilter +if args.hostfilter != r".*": + print(f"Pattern is: {re.escape(pattern)}") + +for host in get_host_details(get_hosts(STALE_DATE, args.tag, args.osfilter)): + # Retrieve host detail + if 'hostname' in host: + if re.findall(pattern, host['hostname']): + stale = parse_host_detail(host, stale) # If we produced stale host results if stale: @@ -245,12 +260,15 @@ def hide_hosts(id_list: list) -> dict: else: # Remove the hosts host_list = [x[1] for x in stale] - remove_result = hide_hosts(host_list) - if remove_result["status_code"] == 202: - for deleted in remove_result["body"]["resources"]: - print(f"Removed host {deleted['id']}") - else: - for deleted in remove_result["body"]["errors"]: - print(f"[{deleted['code']}] {deleted['message']}") + batch_size = 50 + for i in range(0, len(host_list), batch_size): + batch = host_list[i:i + batch_size] + remove_result = hide_hosts(batch) + if remove_result["status_code"] == 202: + for deleted in remove_result["body"]["resources"]: + print(f"Removed host {deleted['id']}") + else: + for deleted in remove_result["body"]["errors"]: + print(f"[{deleted['code']}] {deleted['message']}") else: print("No stale hosts identified for the range specified.")