diff --git a/.github/wordlist.txt b/.github/wordlist.txt index e411f0c5e..31ac3e0d7 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -1380,4 +1380,12 @@ Destom ValueError QueryCasesIdsByFilter SDKDEMO - +kube +KPA +argparse +colorama +Oke +Okumo +Moomaw +Esha +Kumar \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 84a4e73f6..ac318d10e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -97,6 +97,9 @@ This has been a critical element in the development of the FalconPy project. + Nick, `nickforsythbarr` + `nesies` + `David-M-Berry` ++ Oke Okumo, `@okewoma` ++ Alexander Moomaw, `@alhumaw` ++ Esha Kumar, `@exk200006` ## Sponsors diff --git a/samples/authentication/README.md b/samples/authentication/README.md index 8f4dbb6f6..b92be4674 100644 --- a/samples/authentication/README.md +++ b/samples/authentication/README.md @@ -7,7 +7,8 @@ The examples in this folder focus on authentication to CrowdStrike's APIs. - [Azure Key Vault Authentication](#azure-key-vault-authentication) - CrowdStrike API authentication leveraging Azure Key Vault for credential storage. - [AES Authentication](#aes-authentication) - Leverage AES/CBC to encrypt credentials for use with authentication to the CrowdStrike API. -- [AES File Crypt](#aes-file-crypt) - Encrypt arbitrary files with AES/CBC. +- [AES File Crypt](#aes-file-crypt) - Encrypt arbitrary files with AES/CBC +- [AWS Parameter Store](#aws-parameter-store) - CrowdStrike API authentication leveraging AWS Parameter Store for credential storage - [Token Authentication](#token-authentication) - Token Authentication is the original solution for authenticating to a Service Class, and is still fully supported. This example demonstrates how to use Token Authentication to interact with multiple Service Classes. ## Azure Key Vault Authentication @@ -458,6 +459,64 @@ file arguments: Source code for this example can be found [here](aes_file_crypt.py). --- +## AWS Parameter store +This application demonstrates storing CrowdStrike API credentials within the AWS Parameter Store service, and retrieving them to access the CrowdStrike API. + +### Running the program +In order to run this demonstration, you will need access to CrowdStrike API keys. You will also need to set your specific AWS location + +#### Command line arguments +This program accepts the following command line arguments. + +| Argument | Long Argument | Description | +| :-- | :-- | :-- | +| `-h` | `--help` | Display command line help and exit | +| `-k` _CLIENT_ID_PARAMETER_ | `--client_id_parameter` _CLIENT_ID_PARAMETER_ | Name of the Key Vault Secrets parameter storing your API client ID | +| `-s` _CLIENT_SECRET_PARAMETER_ | `--client_secret_parameter` _CLIENT_SECRET_PARAMETER_ | Name of the Key Vault Secrets parameter storing your API client secret | +| `-d` | `--debug`| Enables debugging functionality | + +#### Basic usage + +##### Use this command to test out the sample. + +```shell +python3 aws_parameter_store.py -k FALCON_CLIENT_ID -s FALCON_CLIENT_SECRET +``` +##### Use this command to activate debugging. + +```shell +python3 aws_parameter_store.py -k FALCON_CLIENT_ID -s FALCON_CLIENT_SECRET -d +``` +#### Command-line help +Command-line help is available via the `-h` argument. + +```shell +usage: aws_parameter_store.py [-h] [-k] CLIENT_ID [-s] CLIENT_SECRET [-d] DEGUG + + + ___ ____ __ ____ _______. + / \ \ \ / \ / / / | + / ^ \ \ \/ \/ / | (----` + / /_\ \ \ / \ \ + / _____ \ \ /\ / .----) | +/__/ \__\ \__/ \__/ |_______/ + + ____ __ _____ __ + / __ \____ __________ _____ ___ ___ / /____ _____ / ___// /_____ ________ + / /_/ / __ `/ ___/ __ `/ __ `__ \/ _ \/ __/ _ \/ ___/ \__ \/ __/ __ \/ ___/ _ \ + / ____/ /_/ / / / /_/ / / / / / / __/ /_/ __/ / ___/ / /_/ /_/ / / / __/ + /_/ \__,_/_/ \__,_/_/ /_/ /_/\___/\__/\___/_/ /____/\__/\____/_/ \___/ + + +optional arguments: + -h, --help show this help message and exit + -d, --debug enables degugging + +required arguments: + -k CLIENT_ID, --client_id_parameter CLIENT_ID + -s CLIENT_SECRET, --client_secret_parameter CLIENT_SECRET +``` + ## Token Authentication [Token authentication](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#legacy-authentication) (also referred to as _legacy authentication_) is the process of authenticating to a FalconPy Service Class by providing a previously assigned bearer token directly to the [`auth_token`](https://www.falconpy.io/Usage/Basic-Service-Class-usage.html#legacy-authentication) keyword when instantiating the Service Class. This is the original method of authentication provided by Service Classes, and while it is frequently eschewed in preference to [Direct](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#direct-authentication) and [Object](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#object-authentication) [Authentication](https://www.falconpy.io/Usage/Authenticating-to-the-API.html), there are multiple scenarios where it is still the best option for the situation. diff --git a/samples/authentication/aws_parameter_store.py b/samples/authentication/aws_parameter_store.py index f03882c70..6ba4627cc 100644 --- a/samples/authentication/aws_parameter_store.py +++ b/samples/authentication/aws_parameter_store.py @@ -34,6 +34,7 @@ This application demonstrates storing CrowdStrike API credentials within the AWS Parameter Store service, and retrieving them to access the CrowdStrike API. """ +import logging from argparse import ArgumentParser, RawTextHelpFormatter, Namespace try: import boto3 @@ -64,8 +65,19 @@ def consume_arguments() -> Namespace: default="FALCON_CLIENT_SECRET", dest="client_secret_parameter" ) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) + + parsed = parser.parse_args() + if parsed.debug: + logging.basicConfig(level=logging.DEBUG) + + + return parsed - return parser.parse_args() def get_parameter_store_params(cmd_line: Namespace): @@ -101,9 +113,9 @@ def get_parameter_store_params(cmd_line: Namespace): return returned_client_id, returned_client_secret -def perform_simple_demonstration(client_id: str, client_secret: str): +def perform_simple_demonstration(client_id: str, client_secret: str, debug: bool): """Perform a simple API demonstration using the credentials retrieved.""" - falcon = Hosts(client_id=client_id, client_secret=client_secret) + falcon = Hosts(client_id=client_id, client_secret=client_secret, debug=debug) # Retrieve 500 hosts and sort ascending by hostname aid_lookup = falcon.query_devices_by_filter_scroll(sort="hostname.asc", limit=500) if not aid_lookup["status_code"] == 200: @@ -120,6 +132,9 @@ def perform_simple_demonstration(client_id: str, client_secret: str): if __name__ == "__main__": - # Consume our command line, retrieve our credentials from AWS parameter store + # Consume our command line arguments + args = consume_arguments() + # retrieve our credentials from AWS parameter store + client_id, client_secret = get_parameter_store_params(args) # and then execute a simple API demonstration to prove functionality. - perform_simple_demonstration(*get_parameter_store_params(consume_arguments())) + perform_simple_demonstration(client_id, client_secret) diff --git a/samples/authentication/requirements_aws_parameter_store.txt b/samples/authentication/requirements_aws_parameter_store.txt new file mode 100644 index 000000000..b1876066a --- /dev/null +++ b/samples/authentication/requirements_aws_parameter_store.txt @@ -0,0 +1,2 @@ +boto3 +crowdstrike-falconpy \ No newline at end of file diff --git a/samples/authentication/requirements_token_authentication_example.txt b/samples/authentication/requirements_token_authentication_example.txt new file mode 100644 index 000000000..295ff972a --- /dev/null +++ b/samples/authentication/requirements_token_authentication_example.txt @@ -0,0 +1,4 @@ +boto3 +click +colorama +crowdstrike-falconpy \ No newline at end of file diff --git a/samples/authentication/token_authentication_example.py b/samples/authentication/token_authentication_example.py index 1a3c450ec..95f8a0f49 100644 --- a/samples/authentication/token_authentication_example.py +++ b/samples/authentication/token_authentication_example.py @@ -34,9 +34,11 @@ This sample should run using any version of FalconPy and requires the colorama and click libraries. """ +import logging import os import click import colorama +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace from falconpy import ( CloudConnectAWS, Detects, @@ -54,9 +56,27 @@ BOLD = colorama.Style.BRIGHT ENDMARK = colorama.Style.RESET_ALL - +def consume_arguments() -> Namespace: + parser = ArgumentParser(description=__doc__, fromatter_class=RawTextHelpFormatter) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) + parser.add_argument("-b", "--base-url", + dest="base_url", + help="CrowdStrike cloud region. (auto or usgov1, Default: auto)", + required=False, + default="usgov1" + ) + parsed = parser.parse_args() + if parsed.debug: + logging.basicConfig(level=logging.DEBUG) + + + return parsed # ### BEGIN token simulation -def get_token(): +def get_token(debug=False): """ Generate a token to use for authentication. @@ -95,7 +115,8 @@ def get_token(): ) auth = OAuth2( client_id=falcon_client_id, - client_secret=falcon_client_secret + client_secret=falcon_client_secret, + debug=debug ) # Generate a token auth.token() @@ -176,6 +197,10 @@ def passed(svc_class: str): if __name__ == "__main__": + # Parse command-line arguments and retrieve debug mode setting + args = consume_arguments() + # Authenticate using Falcon API OAuth2 with debug mode enabled if specified + get_token(debug=args.debug) # Test each of these classes to confirm cross collection authentication for Service Classes classes_to_test = [CloudConnectAWS, Detects, Hosts, IOC, Incidents, Intel] # Grab a simulated token and execute the test series diff --git a/samples/containers/README.md b/samples/containers/README.md new file mode 100644 index 000000000..8b8f1cd5a --- /dev/null +++ b/samples/containers/README.md @@ -0,0 +1,105 @@ +![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png) +[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike) + +# Container examples +The examples in this folder focus on leveraging CrowdStrike's Container APIs to discover and manage your container assets. +- [kube_map - Discover your Kubernetes Attack Surface](#Discover-your-Kubernetes-Attack-Surface) + +## Discover your Kubernetes Attack Surface +Discovers Kubernetes assets that are monitored by the Falcon Sensor (clusters, nodes, pods, and containers). + +> [!IMPORTANT] +> Installing the __Kubernetes Protection Agent (KPA)__ on your clusters will result in the most accurate information. + + +### Running the program +In order to run this demonstration, you will need access to CrowdStrike API keys with the following scopes: +| Service Collection | Scope | +| :---- | :---- | +| Kubernetes Protection | __READ__| + +### Execution syntax +This example accepts the following input parameters. +| Parameter | Purpose | +| :--- | :--- | +| `-d`, `--debug` | Enable API debugging. | +| `-c`, `--cluster` | Display all clusters and the number of attached nodes. | +| `-n`, `--node` | Display all nodes including the number of attached, active pods. | +| `-nn`, `--node_name` | Displays pods connected to a specific node. | +| `-t`, `--thread` | Enables asynchronous API calls for faster returns. | +| `-k`, `--key` | Your CrowdStrike Falcon API Client ID | +| `-s`, `--secret` | Your CrowdStrike Falcon API Client Secret | + +Displays the number of clusters, nodes, pods, and containers detected by the Falcon Sensor. +```shell +python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET +``` + +Displays a table of cluster information. +```shell +python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -c +``` + +Displays a table of node information. +```shell +python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -n +``` + +Displays a table of pods based on it's parent node name using the optional threading feature. +```shell +python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -nn "node_name" -t +``` + +Displays API debug logging. +```shell +python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d +``` + +#### Command-line help +Command-line help is available using the `-h` or `--help` parameters. + +```shell +% python3 kube_map.py -h +usage: kube_map.py [-h] -k CLIENT_ID -s CLIENT_SECRET [-d] [-c] [-n] [-nn NODE_NAME] [-t] + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | |::.. . | FalconPy +`-------' `-------' + + _ ___ _ ____ _____ + | |/ / | | | __ )| ____| + | ' /| | | | _ \| _| + | . \| |_| | |_) | |___ + __ __|_|\_\\___/|____/|_____|__ ____ + | \/ | / \ | _ \| _ \| ____| _ \ + | |\/| | / _ \ | |_) | |_) | _| | |_) | + | | | |/ ___ \| __/| __/| |___| _ < + |_| |_/_/ \_\_| |_| |_____|_| \_\ + +This sample utilizes the Kubernetes Protection service collection to map out +your kubernetes assets. Kubernetes assets are found via the Falcon Sensor. + +Creation date: 06.26.23 - alhumaw + +options: + -h, --help show this help message and exit + -d, --debug Enable API debugging + -c, --cluster Display clusters and it's nodes + -n, --node Display nodes and it's pods + -nn NODE_NAME, --node_name NODE_NAME + Display pods connected to a specific node + -t, --thread Enables asynchronous API calls for faster returns + +required arguments: + -k CLIENT_ID, --client_id CLIENT_ID + CrowdStrike API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + CrowdStrike API client secret +``` + +### Example source code +The source code for this example can be found [here](kube_map.py). diff --git a/samples/containers/kube_map.py b/samples/containers/kube_map.py new file mode 100644 index 000000000..95de90260 --- /dev/null +++ b/samples/containers/kube_map.py @@ -0,0 +1,529 @@ +r""" + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | |::.. . | FalconPy +`-------' `-------' + + _ ___ _ ____ _____ + | |/ / | | | __ )| ____| + | ' /| | | | _ \| _| + | . \| |_| | |_) | |___ + __ __|_|\_\\___/|____/|_____|__ ____ + | \/ | / \ | _ \| _ \| ____| _ \ + | |\/| | / _ \ | |_) | |_) | _| | |_) | + | | | |/ ___ \| __/| __/| |___| _ < + |_| |_/_/ \_\_| |_| |_____|_| \_\ + + +This sample utilizes the KubernetesProtection sample to map out +your kubenetes assets. Kubernetes assets are found via the Falcon Sensor. + +Creation date: 06.26.23 - alhumaw + + +""" + + +import logging +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass +from operator import itemgetter +try: + from termcolor import colored # type: ignore +except ImportError as no_termcolor: + raise SystemExit("The termcolor library must be installed.\n" + "Install it with `python3 -m pip install termcolor" + ) from no_termcolor +try: + from tabulate import tabulate # type: ignore +except ImportError as no_tabulate: + raise SystemExit("The tabulate library must be installed.\n" + "Install it with `python3 -m pip install tabulate`." + ) from no_tabulate +try: + from falconpy import KubernetesProtection, APIError +except ImportError as no_falconpy: + raise SystemExit("The CrowdStrike FalconPy library must be installed.\n" + "Install it with `python3 -m pip install crowdstrike-falconpy`." + ) from no_falconpy + +KUBE = r""" + + .... + .-========-. + .-================-. + .:-========================-:. + .:-================================-:. + .:-===================+====================-:. + .:-======================: .-======================-:. + .-=========================== ===========================-. + :==============================+: :+==============================: + =================================: :================================= + -============================+==-- --==+============================- + ==========================-:. .:-========================== + :==========: .-========:. .:========-. :==========: + ============. .-=+=- .::--: :--:.. .-=+=-. .============ + :==============-: :-======: :====+=-. :-==============: + ================== :=========: :=========: =================- + .=================- .:======+. .+======:. -=================. + =================- : .-=+==. .==+=-. : -================= + :================= ===: .:. .:. :=== =================: + ================+: -=====- -=====: :+================ + .================= ========- .. .-======== =================. + -================= .=========: :====: :=+=======. =================- + ================== :===-::.. ====== .::-==+: ================== + -================== .-==-. ==================- + =============----: . .. ::---============= + :==========- ::--===+++- -+=+===---: -==========: + ============:::-==+==. :+======== .. ========+: .==+==-:::============ + -===================== :======= ==== =======: =====================- + -=====================. .=====. ====== .=====. .=====================- + .====================+- .:=: ======== :=-. -+====================. + :=====================: .==========. :=====================: + -=====================: .:::--:::. :=====================- + :======================= -======================: + -===================+- --:.. ..:-- -+===================- + .=================+: ======++====++====== :==================. + :===============. -====================- .===============: + -=============. :+=====================: .=============- + :============+=+========================+=+============: + .-==================================================-. + .================================================. + :============================================: + .==========================================. + .::::::::::::::::::::::::::::::::::::. +""" + + +def parse_command_line() -> Namespace: + """Parse any provided command line arguments and return the namespace.""" + parser = ArgumentParser(description=__doc__, + formatter_class=RawTextHelpFormatter + ) + require = parser.add_argument_group("required arguments") + require.add_argument("-k", "--client_id", + required=True, + help="CrowdStrike API client ID" + ) + require.add_argument("-s", "--client_secret", + required=True, + help="CrowdStrike API client secret" + ) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) + exclusive_group = parser.add_mutually_exclusive_group() + exclusive_group.add_argument("-c", "--cluster", + help="Display clusters and it's nodes", + action="store_true", + default=False + ) + exclusive_group.add_argument("-n", "--node", + help="Display nodes and it's pods", + action="store_true", + default=False + ) + exclusive_group.add_argument("-nn", "--node_name", + help="Display pods connected to a specific node" + ) + parser.add_argument("-t", "--thread", + help="Enables asynchronous API calls for faster returns", + action="store_true", + default=False + ) + + parsed = parser.parse_args() + + if parsed.debug: + logging.basicConfig(level=logging.DEBUG) + + return parsed + + +@dataclass +class Cluster: + """Kubernetes cluster dataclass.""" + + cluster_id: str + cluster_name: str + agent: str + cloud_type: str + node_count: int + + +@dataclass +class Node: + """Kubernetes nodes dataclass.""" + + node_name: str + parent_cluster_name: str + ip: str + architecture: str + cpu: str + storage: str + pod_count: int + + +@dataclass +class Pod: + """Kubernetes pods dataclass.""" + + pod_id: str + pod_name: str + parent_node_name: str + containers: list + namespace: str + container_count: int + + +class KubernetesEnvironment: + """Kubernetes comprehensive environment.""" + + def __init__(self): + self.clusters: list[Cluster] = [] + self.nodes: list[Node] = [] + self.pods: list[Pod] = [] + + def add_clusters(self, cluster: Cluster) -> None: + """Append cluster to list of clusters.""" + self.clusters = cluster + + def add_nodes(self, node: Node) -> None: + """Append node to list of nodes.""" + self.nodes = node + + def add_pods(self, pod: Pod) -> None: + """Append pod to list of pods.""" + self.pods = pod + + +def generate_clusters(falcon: KubernetesProtection, thread: bool) -> list: + """Retrieve and return a list of clusters.""" + print("Locating Clusters...") + if thread: + print("Threading....") + total = falcon.ReadClusterCombined()['body']['meta']['pagination']['total'] + all_resp = concurent_response(falcon, 'ReadClusterCombined', total) + else: + all_resp = normal_response(falcon, "ReadClusterCombined") + clusters = [] + for batch in all_resp: + new_cluster = Cluster( + cluster_id=batch['cluster_id'], + cluster_name=batch['cluster_name'], + agent=batch['agent_status'], + cloud_type=batch['cloud_name'], + node_count=batch['node_count'] + ) + clusters.append(new_cluster) + + return clusters + + +def generate_nodes(falcon: KubernetesProtection, thread: bool) -> list: + """Retrieve and return a list of nodes.""" + print("Discovering Nodes...") + if thread: + print("Threading....") + total = falcon.ReadNodeCombined()['body']['meta']['pagination']['total'] + all_resp = concurent_response(falcon, 'ReadNodeCombined', total) + else: + all_resp = normal_response(falcon, "ReadNodeCombined") + nodes = [] + real_cpu = "" + for batch in all_resp: + new_node = Node( + node_name=batch['node_name'], + parent_cluster_name=batch['cluster_name'], + ip=batch['ipv4'], + architecture=batch['architecture'], + cpu=batch['cpu'], + storage=batch['storage'], + pod_count=(batch['pod_count']) + ) + if len(new_node.cpu) > 0: + cpu_set = new_node.cpu.split("/") + real_cpu = f"{cpu_set[1]}/{cpu_set[0]}" + new_node.cpu = real_cpu + nodes.append(new_node) + + return nodes + + +def generate_pods(falcon: KubernetesProtection, thread: bool) -> list: + """Retrieve and return a list of pods.""" + print("Finding Pods...") + if thread: + total = falcon.ReadPodCombined()['body']['meta']['pagination']['total'] + print("Threading....") + all_resp = concurent_response(falcon, 'ReadPodCombined', total, filt="container_count:>'0'") + else: + all_resp = normal_response(falcon, "ReadPodCombined") + pods = [] + for batch in all_resp: + new_pod = Pod( + pod_id=batch['pod_id'], + pod_name=batch['pod_name'], + parent_node_name=batch['node_name'], + containers=[], + namespace=batch['namespace'], + container_count=batch['container_count'] + ) + container_list = batch['containers'] + if container_list: + for container in container_list: + new_pod.containers.append(container['id']) + pods.append(new_pod) + + return pods + + +def find_active_pods(pods: list[Pod], containers: dict) -> list: + """Cross-references running containers to find active pods.""" + active_pods = [] + for pod in pods: + has_match = False + for container in pod.containers: + if container in containers: + has_match = True + break + if has_match: + active_pods.append(pod) + + return active_pods + + +def generate_containers(falcon: KubernetesProtection, thread: bool) -> dict: + """Retrieve and return a list of RUNNING containers.""" + print("Tracking Down Containers...") + running_containers = {} + filt = "running_status:'true'" + if thread: + total = falcon.ReadContainerCombined(filter=filt)['body']['meta']['pagination']['total'] + all_resp = concurent_response(falcon, "ReadContainerCombined", total, filt) + else: + all_resp = normal_response(falcon, "ReadContainerCombined", filt) + for container in all_resp: + container_id = container.get('container_id') + container_name = container.get('container_name') + running_containers[container_id] = container_name + + return running_containers + + +def response_processing(falcon: KubernetesProtection, + endpoint: str, filt: str, + limit: int, offset: int) -> list: + """Dynamic API caller for multi-proccessing.""" + method = getattr(falcon, endpoint, None) + if method is None: + raise AttributeError(f"API object has no method named '{endpoint}'") + if filt: + resp = method(filter=filt if filt else None, + limit=limit, + offset=offset)['body']['resources'] + else: + resp = method(limit=limit, offset=offset)['body']['resources'] + + return resp + + +def normal_response(falcon: KubernetesProtection, endpoint: str, filt=None) -> list: + """Caller to handle pagination.""" + limit = 200 + all_resp = [] + total = 1 + offset = 0 + method = getattr(falcon, endpoint, None) + if method is None: + raise AttributeError(f"API object has no method named '{endpoint}'") + + while len(all_resp) < total: + resp = method(limit=limit, offset=offset, filter=filt) + if resp['status_code'] == 200: + total = resp['body']['meta']['pagination']['total'] + offset += 200 + all_resp.extend(resp['body']['resources']) + + return all_resp + + +def concurent_response(falcon: KubernetesProtection, endpoint: str, total: str, filt=None) -> list: + """Utilizes concurrent futures to asynchronously handle paginated API calls at once.""" + chunk_size = 200 + # Determine how many workers are needed to handle all chunks at once + workers = int(total / chunk_size) + 1 + # Create a list of chunks of the total, incremented by the max limit (chunk_size) + # (range(0,total), each list index is incremented by 200) + offsets = [i * chunk_size for i in range(workers)] + all_resp = [] + if workers > 10: + # Splits the list into batches of 10 to distribute load + batches = [offsets[x: x + 10] for x in range(0, len(offsets), 10)] + for batch in batches: + with ThreadPoolExecutor(max_workers=workers) as e: + future = { + e.submit(response_processing, falcon, endpoint, filt, chunk_size, offset) + for offset in batch + } + for f in future: + all_resp.extend(f.result()) + else: + with ThreadPoolExecutor(max_workers=workers) as e: + future = { + e.submit(response_processing, falcon, endpoint, filt, chunk_size, offset) + for offset in offsets + } + for f in future: + all_resp.extend(f.result()) + + return all_resp + + +def aggregate_kube(clusters: list, nodes: list, pods=None) -> KubernetesEnvironment: + """Organizes clusters, nodes, and pods into a data structure.""" + kube = KubernetesEnvironment() + kube.add_clusters(clusters) + kube.add_nodes(nodes) + if pods: + kube.add_pods(pods) + + return kube + + +def connect_api(key: str, secret: str, debug: bool) -> KubernetesProtection: + """Connect and return an instance of the Uber class.""" + try: + if debug: + logging.basicConfig(level=logging.DEBUG) + return KubernetesProtection(client_id=key, client_secret=secret, debug=debug) + except APIError as e: + print(f"Failed to connect to API: {e}") + return e + + +def form_relations(kube: KubernetesEnvironment, args: Namespace) -> dict: + """Return a tabulated kubernetes environment.""" + if args.cluster: + cluster_data = [] + for cluster in kube.clusters: + for node in kube.nodes: + if node.parent_cluster_name == cluster.cluster_name: + cluster.node_count += 1 + cluster_data.append([ + cluster.cluster_name if cluster.cluster_name else cluster.cluster_id, + cluster.agent, + cluster.cloud_type, + cluster.node_count + ]) + headers = ['Cluster Name || ID', 'KPA Status', 'Cloud Type', 'Node Count'] + # Sort by node count + cluster_data.sort(key=itemgetter(3)) + + return tabulate(cluster_data, headers, tablefmt='grid') + + if args.node: + node_data = [] + for node in kube.nodes: + for pod in kube.pods: + if pod.parent_node_name == node.node_name: + node.pod_count += 1 + node_data.append([ + node.node_name, + node.ip, + node.architecture, + node.cpu, + node.storage, + node.pod_count + ]) + headers = ['Node Name', 'IP', 'Arch', 'CPU', 'Storage', 'Active\nPod Count'] + # Sort by pod count + node_data.sort(key=itemgetter(5)) + + return tabulate(node_data, headers, tablefmt='grid') + + pod_data = [] + for pod in kube.pods: + if pod.parent_node_name == args.node_name: + pod_data.append([ + pod.pod_id, + pod.pod_name, + pod.namespace, + pod.container_count + ]) + headers = ['Pod ID', 'Pod Name', 'Namespace', 'Container Count'] + if len(pod_data) < 1: + return f"Found 0 pods related to '{args.node_name}'" + + return tabulate(pod_data, headers, tablefmt='grid') + + +def find_asset_count(falcon: KubernetesProtection) -> dict: + """Find and output the initial count of all assets.""" + env = {} + containers = colored(falcon.ReadContainerCount( + filter="running_status: 'true'")['body']['resources'][0]['count'], 'red') + + pods = colored(falcon.ReadPodCount()['body']['resources'][0]['count'], 'red') + nodes = colored(falcon.ReadNodeCount()['body']['resources'][0]['count'], 'red') + clusters = colored(falcon.ReadClusterCount()['body']['resources'][0]['count'], 'red') + env = { + 'Asset': [colored('Clusters', "yellow"), + colored('Nodes', "blue"), + colored('Pods', 'green'), + 'Containers'], + 'Count': [clusters, nodes, pods, containers] + } + + return (tabulate(env, headers="keys", tablefmt='heavy_grid', colalign=("left", "left"))) + + +def print_kube(args: Namespace, falcon: KubernetesProtection) -> None: + """Print the kubernetes environment to the terminal.""" + if args.cluster or args.node or args.node_name: + clusters = generate_clusters(falcon, args.thread) + nodes = generate_nodes(falcon, args.thread) + + if args.node or args.node_name: + # Compare all pods against active containers to find active pods + pods = generate_pods(falcon, args.thread) + containers = generate_containers(falcon, args.thread) + active_pods = find_active_pods(pods, containers) + # Creates kube instance, prints out table + kube = aggregate_kube(clusters, nodes, active_pods) + kube_table = form_relations(kube, args) + print(kube_table) + + else: + kube = aggregate_kube(clusters, nodes) + kube_table = form_relations(kube, args) + print(kube_table) + + else: + print(KUBE) + print(find_asset_count(falcon)) + cluster_info = f"Use {colored('-c', 'yellow')} to print cluster information" + node_info = f"Use {colored('-n', 'blue')} to print node information" + pod_info = (f"Use {colored('-nn', 'green')}" + " with a node name to print\nactive pods linked to that node") + hint = (f"HINT: use {colored('-t', 'magenta')}" + " along with any command to\nimprove speed using asynchronous processing") + print(tabulate([[cluster_info], [node_info], [pod_info], [hint]], tablefmt="mixed_grid")) + + +def main(): + """Start Main Execution Routine.""" + args = parse_command_line() + falcon = connect_api(key=args.client_id, secret=args.client_secret, debug=args.debug) + print_kube(args, falcon) + + +if __name__ == "__main__": + main() diff --git a/samples/cspm_registration/README.md b/samples/cspm_registration/README.md index a398816b8..c960ee01d 100644 --- a/samples/cspm_registration/README.md +++ b/samples/cspm_registration/README.md @@ -48,6 +48,10 @@ python3 get_cspm_policies.py -f $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -o fi python3 get_cspm_policies.py -f $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -c aws ``` +```shell +python3 get_cspm_policies.py -f $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d +``` +> To activate debugging, use the `-d` argument. #### Command-line help Command-line help is available via the `-h` argument. @@ -98,7 +102,8 @@ optional arguments: Policy report output file (CSV format) -c CLOUD, --cloud CLOUD Cloud provider (aws, azure, gcp) + -d, --debug, Activates debugging ``` ### Example source code -The source code for this example can be found [here](get_cspm_policies.py). \ No newline at end of file +The source code for this example can be found [here](get_cspm_policies.py). diff --git a/samples/cspm_registration/get_cspm_policies.py b/samples/cspm_registration/get_cspm_policies.py index 7efb1e73f..3d071ab52 100644 --- a/samples/cspm_registration/get_cspm_policies.py +++ b/samples/cspm_registration/get_cspm_policies.py @@ -39,7 +39,7 @@ import os import sys import logging -from argparse import ArgumentParser, RawTextHelpFormatter +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace from tabulate import tabulate try: from falconpy import CSPMRegistration @@ -49,17 +49,42 @@ ) from no_falconpy +def consume_arguments() -> Namespace: # Capture command line 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", - help="Falcon Client Secret", default=None, required=False) -parser.add_argument("-o", "--output_file", - help="Policy report output file (CSV format)", required=False) -parser.add_argument( - "-c", "--cloud", help="Cloud provider (aws, azure, gcp)", required=False) -args = parser.parse_args() + 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", + help="Falcon Client Secret", + default=None, + required=False) + parser.add_argument("-o", "--output_file", + help="Policy report output file (CSV format)", + required=False) + parser.add_argument("-c", "--cloud", + help="Cloud provider (aws, azure, gcp)", + required=False) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) + + parsed = parser.parse_args() + return parsed + + +cmd_line = consume_arguments() + +# Activate debugging if requested +if cmd_line.debug: + logging.basicConfig(level=logging.DEBUG) + + + +# pylint: disable=E0606 # Grab our client_id and client_secret or exit CONFIG_FILE = '../config.json' @@ -68,20 +93,22 @@ config = json.loads(file_config.read()) falcon_client_id = config['falcon_client_id'] falcon_client_secret = config['falcon_client_secret'] -elif args.falcon_client_id is not None and args.falcon_client_secret is not None: - falcon_client_id = args.falcon_client_id - falcon_client_secret = args.falcon_client_secret +elif cmd_line.falcon_client_id is not None and cmd_line.falcon_client_secret is not None: + falcon_client_id = cmd_line.falcon_client_id + falcon_client_secret = cmd_line.falcon_client_secret + debug = cmd_line.debug if cmd_line.debug else False # Set debug mode based on argument else: logging.error( " Please specify Falcon API Credentials with config.json or script arguments") sys.exit() -data_file = args.output_file -cloud = args.cloud +data_file = cmd_line.output_file +cloud = cmd_line.cloud # Instantiate CSPM_Registration service class falcon = CSPMRegistration(client_id=falcon_client_id, - client_secret=falcon_client_secret + client_secret=falcon_client_secret, + debug=debug ) diff --git a/samples/cspm_registration/requirements_get_cspm_policies.txt b/samples/cspm_registration/requirements_get_cspm_policies.txt new file mode 100644 index 000000000..598e20cb7 --- /dev/null +++ b/samples/cspm_registration/requirements_get_cspm_policies.txt @@ -0,0 +1,2 @@ +crowdstrike-falconpy +tabulate \ No newline at end of file diff --git a/samples/discover/README.md b/samples/discover/README.md index 3c6f5df71..bbaf59e85 100644 --- a/samples/discover/README.md +++ b/samples/discover/README.md @@ -55,6 +55,12 @@ python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET - python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -f simple ``` +> Activate API debugging with the `-d` argument. + +```shell +python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d +``` + ##### Available table formats Tabular results may be formatted using any of the format options listed below. @@ -87,7 +93,7 @@ Command-line help is available via the `-h` argument. ```shell python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -h -usage: list_discovered_hosts.py [-h] [-k CLIENT_ID] [-s CLIENT_SECRET] [-b BASE_URL] [-r] [-f FORMAT] +usage: list_discovered_hosts.py [-h] [-k CLIENT_ID] [-s CLIENT_SECRET] [-b BASE_URL] [-r] [-d] [-f FORMAT] CrowdStrike Falcon Discover simple example. @@ -115,7 +121,7 @@ CrowdStrike Falcon Discover simple example. Creation date: 02.08.2022 - jshcodes@CrowdStrike -optional arguments: +options: -h, --help show this help message and exit -k CLIENT_ID, --client_id CLIENT_ID CrowdStrike Falcon API key ID. @@ -126,6 +132,7 @@ optional arguments: -b BASE_URL, --base_url BASE_URL CrowdStrike API region (us1, us2, eu1, usgov1) NOT required unless you are using `usgov1`. -r, --reverse Reverse sort (defaults to ASC) + -d, --debug Enable API debugging -f FORMAT, --format FORMAT Table format to use for display. (plain, simple, github, grid, fancy_grid, pipe, orgtbl, jira, presto, diff --git a/samples/discover/list_discovered_hosts.py b/samples/discover/list_discovered_hosts.py index abea9c413..28b01fa73 100644 --- a/samples/discover/list_discovered_hosts.py +++ b/samples/discover/list_discovered_hosts.py @@ -25,36 +25,36 @@ Creation date: 02.08.2022 - jshcodes@CrowdStrike """ import os +import logging from argparse import ArgumentParser, RawTextHelpFormatter from tabulate import tabulate try: - from falconpy import Discover + from falconpy import Discover, Hosts except ImportError as no_falconpy: raise SystemExit("The crowdstrike-falconpy package must be installed " - "in order to run this progrem.\n\nInstall with the command: " + "in order to run this program.\n\nInstall with the command: " "python3 -m pip install crowdstrike-falconpy") from no_falconpy - def parse_command_line() -> object: """Parse any received inbound command line parameters.""" parser = ArgumentParser( description=__doc__, formatter_class=RawTextHelpFormatter - ) + ) parser.add_argument( '-k', '--client_id', help='CrowdStrike Falcon API key ID.\n' 'You can also use the `FALCON_CLIENT_ID` environment variable to specify this value.', required=False - ) + ) parser.add_argument( '-s', '--client_secret', help='CrowdStrike Falcon API key secret.\n' 'You can also use the `FALCON_CLIENT_SECRET` environment variable to specify this value.', required=False - ) + ) parser.add_argument( '-b', '--base_url', @@ -68,7 +68,15 @@ def parse_command_line() -> object: help='Reverse sort (defaults to ASC)', required=False, action="store_true" - ) + ) + parser.add_argument( + '-d', + '--debug', + help='Enable API debugging', + required=False, + default=False, + action="store_true" + ) parser.add_argument( '-f', '--format', @@ -77,19 +85,13 @@ def parse_command_line() -> object: 'pretty, psql, rst, mediawiki, moinmoin, youtrack, html, unsafehtml, \n' 'latext, latex_raw, latex_booktabs, latex_longtable, textile, tsv)', required=False - ) - + ) return parser.parse_args() - - def get_sort_key(sorting) -> list: - """Return the sort colum value for sorting operations.""" + """Return the sort column value for sorting operations.""" return sorting["hostname"] - - -# Retrieve all inbound command line parameters +# Retrieve all inbound command line parameters if args debug is present args = parse_command_line() - # Set constants based upon received inputs BASE_URL = "auto" if args.base_url: @@ -106,6 +108,10 @@ def get_sort_key(sorting) -> list: SORT = False else: SORT = bool(args.reverse) +# add debug with logging put after parser +if args.debug: + logging.basicConfig(level=logging.DEBUG) + TABLE_FORMATS = [ "plain", "simple", "github", "grid", "fancy_grid", "pipe", "orgtbl", "jira", "presto", "pretty", "psql", "rst", "mediawiki", "moinmoin", "youtrack", "html", "unsafehtml", @@ -116,7 +122,6 @@ def get_sort_key(sorting) -> list: table_format = args.format.strip().lower() if table_format in TABLE_FORMATS: TABLE_FORMAT = table_format - # Headers used in our result display table HEADERS = { "hostname": "Hostname", @@ -125,18 +130,18 @@ def get_sort_key(sorting) -> list: "plat": "Platform", "osver": "Version" } - +hosts = Hosts( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + debug=args.debug +) # Connect to the Discover API -discover = Discover(client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - base_url=BASE_URL - ) - +discover = Discover(auth_object=hosts) # Empty list to hold our results identified = [] # Query for a complete list of discovered hosts. Maxes out at 100. host_lookup = discover.query_hosts() -if host_lookup["status_code"] == 200: +if host_lookup.get("status_code") == 200: identified_hosts = host_lookup["body"]["resources"] if not identified_hosts: # No hosts returned for this search @@ -144,14 +149,15 @@ def get_sort_key(sorting) -> list: else: # Retrieve all details for all discovered hosts host_detail = discover.get_hosts(ids=identified_hosts)["body"]["resources"] - # Add each hosts relevant detail to our `identified` list so we can display it + # Add each host's relevant detail to our `identified` list so we can display it for host in host_detail: - found = {} - found["hostname"] = host.get("hostname", "Not identified") - found["current_local"] = host.get("current_local_ip", "Unknown") - found["current_external"] = host.get("external_ip", "Unknown") - found["plat"] = host.get("platform_name", "Unknown") - found["osver"] = host.get("os_version", "Unknown") + found = { + "hostname": host.get("hostname", "Not identified"), + "current_local": host.get("current_local_ip", "Unknown"), + "current_external": host.get("external_ip", "Unknown"), + "plat": host.get("platform_name", "Unknown"), + "osver": host.get("os_version", "Unknown") + } # Append this result to our display list identified.append(found) # All findings have been tabulated, show the results diff --git a/samples/discover/spyglass.py b/samples/discover/spyglass.py index 9f1a2b2c3..b65b2a2b6 100644 --- a/samples/discover/spyglass.py +++ b/samples/discover/spyglass.py @@ -29,6 +29,7 @@ # pylint: disable=R0902,R0904,R0912,R0914,R0915 import os import json +import logging from datetime import datetime from argparse import ArgumentParser, RawTextHelpFormatter from concurrent.futures import ThreadPoolExecutor @@ -66,6 +67,8 @@ class Application: """Class to store configuration and performance detail.""" + _debug = False + _timing = { "start_time": datetime.now().timestamp(), "end_time": 0, @@ -88,6 +91,7 @@ class Application: _status = {"running": True, "cancelled": False} def __init__(self): + """Construct an instance of the application.""" self.configure_application() if not self.show_updates: print("Depending on the size of your environment, " @@ -214,6 +218,11 @@ def configure_application(self): default=None, required=False ) + parser.add_argument("--debug", + help="Enable API debugging", + action="store_true", + default=False + ) parsed = parser.parse_args() cats = parsed.categories.split(",") if "all" in cats: @@ -243,11 +252,16 @@ def configure_application(self): self.applications_filter = parsed.applications_filter if parsed.applications_sort: self.applications_sort = parsed.applications_sort + if parsed.debug: + self.debug = True + self.show_updates = False + logging.basicConfig(level=logging.DEBUG) # Everything before this moment happens within milliseconds self.sdk = Discover(client_id=parsed.falcon_client_id, client_secret=parsed.falcon_client_secret, - base_url=parsed.region + base_url=parsed.region, + debug=self.debug ) self.hosts = Hosts(auth_object=self.sdk) @@ -288,6 +302,16 @@ def extra(self) -> dict: return self._configuration["extra"] # Mutable + @property + def debug(self) -> bool: + """Retrieve the end time property.""" + return self._debug + + @debug.setter + def debug(self, val: bool): + """Set the end time property.""" + self._debug = val + @property def end_time(self) -> int: """Retrieve the end time property.""" @@ -463,7 +487,7 @@ def batch_get_details(sdk, cat): def get_detail(ids): """Retrieve detail information for the ID list provided.""" returned = False - if APP.running: + if APP.running: # pylint: disable=E0606 APP.api_calls += 1 returned = cmd(ids=ids)["body"]["resources"] return returned @@ -492,9 +516,9 @@ def get_detail(ids): details.extend(get_detail(returned)) if APP.show_updates: print(f" Details for {len(details)} {cat} retrieved.", - end=f"{' '*40}\r", - flush=True - ) + end=f"{' '*40}\r", + flush=True + ) if running_total >= total or not APP.running: running = False else: @@ -670,7 +694,7 @@ def display_accounts(account_list: list): row_break() -def display_hosts(hosts_list: list): +def display_hosts(hosts_list: list): # noqa """Display the hosts results.""" print(category_header("Hosts"), end="\r") row_break(23) @@ -697,6 +721,7 @@ def display_hosts(hosts_list: list): for host in hosts_list: first_seen = '' last_seen = '' + managed = '' if host.get("first_seen_timestamp", None): first_seen = datetime.strptime(host["first_seen_timestamp"], "%Y-%m-%dT%H:%M:%SZ") first_seen = bold(first_seen.strftime("%m-%d-%Y %H:%M:%S")) diff --git a/samples/intel/README.md b/samples/intel/README.md index ab740eb87..132059f6b 100644 --- a/samples/intel/README.md +++ b/samples/intel/README.md @@ -416,12 +416,42 @@ Total indicators: 1 Execution time: 2.86 seconds ``` +Show the debug output. +```shell +python3 intel_search.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -f "FROZEN SPIDER" -d +``` +##### Result +```shell +DEBUG:falconpy._auth_object._falcon_interface:CREATED: OAuth2 interface class +DEBUG:falconpy._auth_object._falcon_interface:AUTH: Configured for Direct Authentication +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Base URL set to https://api.crowdstrike.com +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: SSL verification is set to True +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Timeout set to None seconds +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Proxy dictionary: None +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: User-Agent string set to: None +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Token renewal window set to 120 seconds +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Maximum number of records to log: 100 +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Log sanitization is enabled +DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Pythonic responses are disabled +DEBUG:falconpy._auth_object._falcon_interface:OPERATION: oauth2AccessToken +DEBUG:falconpy._auth_object._falcon_interface:ENDPOINT: https://api.crowdstrike.com/oauth2/token (POST) +DEBUG:falconpy._auth_object._falcon_interface:HEADERS: {'User-Agent': 'crowdstrike-falconpy/1.4.4', 'CrowdStrike-SDK': 'crowdstrike-falconpy/1.4.4'} +DEBUG:falconpy._auth_object._falcon_interface:PARAMETERS: None +DEBUG:falconpy._auth_object._falcon_interface:BODY: None +DEBUG:falconpy._auth_object._falcon_interface:DATA: {'client_id': 'REDACTED', 'client_secret': 'REDACTED'} +DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.crowdstrike.com:443 +``` + #### Command-line help Command-line help is available via the `-h` argument. ```shell python3 intel_search.py -h -usage: intel_search.py [-h] -f FIND -k CLIENT_ID -s CLIENT_SECRET [-r] [-t TYPES] [-tf TABLE_FORMAT] [-o OUTPUT_PREFIX] + +CrowdStrike Falcon Intel API search example using the FalconPy library. + +usage: intel_search.py [-h] -f FIND -k CLIENT_ID -s CLIENT_SECRET [-r] [-t TYPES] [-tf TABLE_FORMAT] + [-o OUTPUT_PREFIX] [-d] CrowdStrike Falcon Intel API search example using the FalconPy library. @@ -443,7 +473,13 @@ A maximum of 50,000 results per category will be returned. Creation date: 03.30.23 - jshcodes@CrowdStrike -optional arguments: +This application requires: + pyfiglet + termcolor + tabulate + crowdstrike-falconpy v1.3.0+ + +options: -h, --help show this help message and exit -r, --reverse Reverse the sort. -t TYPES, --types TYPES @@ -452,6 +488,7 @@ optional arguments: Set the table format. -o OUTPUT_PREFIX, --output_prefix OUTPUT_PREFIX Output filename prefix for storing results (CSV format). + -d, --debug Enable API debugging required arguments: -f FIND, --find FIND Search string to identify diff --git a/samples/intel/intel_search.py b/samples/intel/intel_search.py index e4b4353da..f5dbb3267 100644 --- a/samples/intel/intel_search.py +++ b/samples/intel/intel_search.py @@ -18,8 +18,15 @@ A maximum of 50,000 results per category will be returned. Creation date: 03.30.23 - jshcodes@CrowdStrike + +This application requires: + pyfiglet + termcolor + tabulate + crowdstrike-falconpy v1.3.0+ """ +import logging from argparse import ArgumentParser, RawTextHelpFormatter, Namespace from concurrent.futures import ThreadPoolExecutor from csv import writer, QUOTE_ALL @@ -109,11 +116,19 @@ def parse_command_line(): help="Output filename prefix for storing results (CSV format).", default=None ) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) parsed = parser.parse_args() allow = ["indicator", "report", "actor"] parsed.types = [t for t in parsed.types.split(",") if t in allow] if parsed.types else allow + if parsed.debug: + logging.basicConfig(level=logging.DEBUG) + return parsed @@ -122,6 +137,7 @@ def bold(val: str): return colored(val, attrs=["bold"]) +# pylint: disable=E0606 def batch_get(func: object, filt: str, catg: str): """Asynchronously retrieve Falcon Intelligence API results.""" offset = 0 @@ -167,7 +183,7 @@ def chunk_long_description(desc: str, col_width: int = 120) -> str: def simple_list_display(keyval: str, record: dict, title: str, no_val: bool = False): - """Generic handler for displaying information provided as simple lists.""" + """Dynamic handler for displaying information provided as simple lists.""" if keyval in record: if len(record[keyval]): if no_val: @@ -178,12 +194,12 @@ def simple_list_display(keyval: str, record: dict, title: str, no_val: bool = Fa def large_list_display(keyval: str, record: dict, title: str): - """Generic handler for displaying list information with an underlined header.""" + """Dynamic handler for displaying list information with an underlined header.""" if keyval in record: if len(record[keyval]): res = ", ".join(t["value"].title() for t in record[keyval]) res = f"{chunk_long_description(res)}" - res = f"{colored(title, attrs=['bold','underline'])}\n{res}" + res = f"{colored(title, attrs=['bold', 'underline'])}\n{res}" print(f"{res}\n") @@ -565,7 +581,7 @@ def show_result_totals(act_cnt: int, ind_cnt: int, rep_cnt: int, typ_list: list) def main(args: Namespace): """Search for a specified string and identify if it matches an indicator, report, or actor.""" # Perform the search using an authenticated instance of the Intel Service Class - ret = perform_search(Intel(client_id=args.client_id, client_secret=args.client_secret), + ret = perform_search(Intel(client_id=args.client_id, client_secret=args.client_secret, debug=args.debug), args.find, # Search string args.types, # Types to display args.table_format, # Table format diff --git a/samples/ioc/create_ioc.py b/samples/ioc/create_ioc.py index 3d3b77878..df39099ae 100644 --- a/samples/ioc/create_ioc.py +++ b/samples/ioc/create_ioc.py @@ -24,6 +24,7 @@ "applied_globally": true } """ +import logging import json import os from argparse import ArgumentParser, RawTextHelpFormatter @@ -32,12 +33,34 @@ def consume_command_line(): parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) - parser.add_argument("-k", "--falcon_client_id", help="Falcon API Client ID", required=True) - parser.add_argument("-s", "--falcon_client_secret", help="Falcon API Client Secret", required=True) - parser.add_argument("-m", "--method", help="SDK method to use ('service' or 'uber').", required=False, default="service") - parser.add_argument("-i", "--indicator", help="Path to the file representing the indicator (JSON format).", default="example_indicator.json", required=False) + parser.add_argument("-k", "--falcon_client_id", + help="Falcon API Client ID", + required=True) + parser.add_argument("-s", "--falcon_client_secret", + help="Falcon API Client Secret", + required=True) + parser.add_argument("-m", "--method", + help="SDK method to use ('service' or 'uber').", + required=False, + default="service") + parser.add_argument("-i", "--indicator", + help="Path to the file representing the indicator (JSON format).", + default="example_indicator.json", + required=False) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) + + + parsed = parser.parse_args() - return parser.parse_args() + if parsed.debug: + logging.basicConfig(level=logging.DEBUG) + + + return parsed def connect_api(class_type: str = "service", creds: dict = None): @@ -58,7 +81,7 @@ def connect_api(class_type: str = "service", creds: dict = None): if args.method not in ["service", "uber"]: args.method = "service" -falcon = connect_api(args.method, credentials) +falcon = connect_api(args.method, credentials, args.debug) if not os.path.exists(args.indicator): raise SystemExit("Unable to load indicator file.") diff --git a/samples/prevention_policy/README.md b/samples/prevention_policy/README.md index eaa970d48..c7accf5e0 100644 --- a/samples/prevention_policy/README.md +++ b/samples/prevention_policy/README.md @@ -270,7 +270,7 @@ In order to run this demonstration, you will need access to CrowdStrike API keys This example accepts the following input parameters. | Parameter | Purpose | Category | | :--- | :--- | :--- | -| `-h`, `--help` | Show help message and exit | optional | +| `-h`, `--help` | Show help message and exit | optional | | `-r`, `--show_settings` | Display policy settings | display | | `-z`, `--verbose` | Show all settings, including disabled | display | | `-e`, `--enable` | Enable the policy | administration | @@ -517,6 +517,7 @@ optional management arguments: -e, --enable Enable the policy -d, --disable Disable the policy -x, --delete Delete the policy + -debug, --debug Enable API debugging optional update arguments: -i POLICY_ID, --policy_id POLICY_ID diff --git a/samples/prevention_policy/prevention_policy_hawk.py b/samples/prevention_policy/prevention_policy_hawk.py index f7d76aaf8..be0711907 100644 --- a/samples/prevention_policy/prevention_policy_hawk.py +++ b/samples/prevention_policy/prevention_policy_hawk.py @@ -20,7 +20,7 @@ | | | | | | | | \ / | |\ \ |___| |___| |___| |___| \____/\/\____/ |___| \___\ |0\/0| - \/\/ FalconPy v1.0 + \/\/ FalconPy v1.4.4 \/ Creation date: 2022.02.11 Modification: 2022.05.11 @@ -33,6 +33,7 @@ This solution requires the FalconPy SDK. This project can be accessed here: https://github.com/CrowdStrike/falconpy """ +import logging from argparse import ArgumentParser, RawTextHelpFormatter from enum import Enum from tabulate import tabulate @@ -352,6 +353,11 @@ def consume_command_line(): parser = ArgumentParser(description=shiny_description(__doc__), formatter_class=RawTextHelpFormatter ) + parser.add_argument("-debug", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) # Display view = parser.add_argument_group("optional display arguments") view.add_argument("-r", "--show_settings", @@ -414,11 +420,19 @@ def consume_command_line(): ) # Always required req = parser.add_argument_group("required arguments") - req.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) - req.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) + req.add_argument("-f", "--falcon_client_id", + help="Falcon Client ID", + required=True) + req.add_argument("-s", "--falcon_client_secret", + help="Falcon Client Secret", + required=True) - return parser.parse_args() + parsed = parser.parse_args() + if parsed.debug: + logging.basicConfig(level=logging.DEBUG) + + return parsed def do_policy_delete(pol_id: str = None): """Delete the policy using the provided ID.""" @@ -603,9 +617,10 @@ def process_arguments(command, # Retrieve any provided command line arguments args = consume_command_line() - # Authenticate using our provided falcon client_id and client_secret + # Authenticate using our provided falcon client_id and client_secret and debugging if activated falcon_policy = PreventionPolicy(client_id=args.falcon_client_id, - client_secret=args.falcon_client_secret + client_secret=args.falcon_client_secret, + debug=args.debug ) # Review the provided arguments and then perform the request diff --git a/samples/prevention_policy/requiremts_prevention_policy_hawk.txt b/samples/prevention_policy/requiremts_prevention_policy_hawk.txt new file mode 100644 index 000000000..6e933ceee --- /dev/null +++ b/samples/prevention_policy/requiremts_prevention_policy_hawk.txt @@ -0,0 +1,4 @@ +argparse +enum34 +crowdstrike-falconpy +tabulate diff --git a/samples/recon/email_monitoring_recon.py b/samples/recon/email_monitoring_recon.py index 9dec83274..f94cc93d5 100644 --- a/samples/recon/email_monitoring_recon.py +++ b/samples/recon/email_monitoring_recon.py @@ -8,6 +8,7 @@ Creation: 06.21.2022, wozboz@CrowdStrike """ +import logging from csv import reader from argparse import ArgumentParser, RawTextHelpFormatter from falconpy import Recon @@ -33,15 +34,21 @@ help="File with email-addresses to use as input", required=True, ) - +parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) args = parser.parse_args() +if args.debug: + logging.basicConfig(level=logging.DEBUG) EMAIL_FILE = args.file falcon = Recon(client_id=args.falcon_client_id, client_secret=args.falcon_client_secret, - base_url=args.base_url + base_url=args.base_url, debug=args.debug ) QUERY = "(" diff --git a/samples/sensor_download/README.md b/samples/sensor_download/README.md index 18846bb74..5b3befb7b 100644 --- a/samples/sensor_download/README.md +++ b/samples/sensor_download/README.md @@ -41,7 +41,9 @@ This program accepts the following command-line arguments. | `-o` _OS_ | `--os` _OS_ | Sensor operating system | | `-v` _OSVER_ | `--osver` _OSVER_ | Sensor operating system version | | `-f` _FILENAME_ | `--filename` _FILENAME_ | Name to use for downloaded file | -| `-t` _TABLE_FORMAT_ | `--table_format` _TABLE_FORMAT_ | Table format to use for display.