From c4881715e5231bec4d1c5ba414c89b17b35b76b6 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Fri, 2 Apr 2021 11:46:18 -0400 Subject: [PATCH 01/37] Update label_request.yml Labeller fails on forks, removing workflow from dev branches. --- .github/workflows/label_request.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/label_request.yml b/.github/workflows/label_request.yml index 70cf621c9..0d6f448a8 100644 --- a/.github/workflows/label_request.yml +++ b/.github/workflows/label_request.yml @@ -3,7 +3,6 @@ on: pull_request: branches: - main - - 'ver_*' jobs: triage: From 1d13967e7e1798ec0c264fa601d685a0c6bbbf26 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 13:31:38 -0400 Subject: [PATCH 02/37] Sample code: Sample Uploads API --- samples/sample_uploads/README.md | 1 + samples/sample_uploads/sample_uploads.py | 97 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 samples/sample_uploads/README.md create mode 100644 samples/sample_uploads/sample_uploads.py diff --git a/samples/sample_uploads/README.md b/samples/sample_uploads/README.md new file mode 100644 index 000000000..fa9138adc --- /dev/null +++ b/samples/sample_uploads/README.md @@ -0,0 +1 @@ +# FalconPy usage examples \ No newline at end of file diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py new file mode 100644 index 000000000..fd6bfa881 --- /dev/null +++ b/samples/sample_uploads/sample_uploads.py @@ -0,0 +1,97 @@ +# ____ _ _ _ _ _ +# / ___| __ _ _ __ ___ _ __ | | ___ | | | |_ __ | | ___ __ _ __| |___ +# \___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | | '_ \| |/ _ \ / _` |/ _` / __| +# ___) | (_| | | | | | | |_) | | __/ | |_| | |_) | | (_) | (_| | (_| \__ \ +# |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ +# |_| |_| + +# _ ____ ___ _____ _ +# / \ | _ \_ _| | ____|_ ____ _ _ __ ___ _ __ | | ___ ___ +# / _ \ | |_) | | | _| \ \/ / _` | '_ ` _ \| '_ \| |/ _ \/ __| +# / ___ \| __/| | | |___ > < (_| | | | | | | |_) | | __/\__ \ +# /_/ \_\_| |___| |_____/_/\_\__,_|_| |_| |_| .__/|_|\___||___/ +# |_| +# +# These examples show how to interact with the Sample Uploads API using +# both the Uber class, and the regular Service class. +# +import json + +# _ _ _ ____ _ +# | | | | |__ ___ _ __ / ___| | __ _ ___ ___ +# | | | | '_ \ / _ \ '__| | | | |/ _` / __/ __| +# | |_| | |_) | __/ | | |___| | (_| \__ \__ \ +# \___/|_.__/ \___|_| \____|_|\__,_|___/___/ +# +# This example shows how to interact with the +# Sample Uploads API using the Uber class. + +from falconpy import api_complete as FalconSDK + +# Grab our config parameters +with open('config.json', 'r') as file_config: + config = json.loads(file_config.read()) + +# Create an instance of the Uber class +falcon = FalconSDK.APIHarness(creds={ + "client_id": config["falcon_client_id"], + "client_secret": config["falcon_client_secret"] + } +) + +# Define our file +FILENAME = "testfile.jpg" +# Open the file for binary read, this will be our payload +PAYLOAD = open(FILENAME, 'rb').read() +# Upload the file using the Sample Uploads API, name this file "newfile.jpg" in the API +response = falcon.command('UploadSampleV3', file_name="newfile.jpg", data=PAYLOAD, content_type="application/octet-stream") +# Grab the SHA256 unique identifier for the file we just uploaded +sha = response["body"]["resources"][0]["sha256"] +# Download a copy of this file, use the SHA256 ID to retrieve it +response = falcon.command("GetSampleV3", ids=sha) +# Save the result to a new file +open('uberclass.jpg', 'wb').write(response) +# Delete the file from the API +response = falcon.command("DeleteSampleV3", ids=sha) +# Print the results of our delete command +print(json.dumps(response, indent=4)) + + +# ____ _ ____ _ +# / ___| ___ _ ____ _(_) ___ ___ / ___| | __ _ ___ ___ +# \___ \ / _ \ '__\ \ / / |/ __/ _ \ | | | |/ _` / __/ __| +# ___) | __/ | \ V /| | (_| __/ | |___| | (_| \__ \__ \ +# |____/ \___|_| \_/ |_|\___\___| \____|_|\__,_|___/___/ +# +# This example shows how to interact with the +# Sample Uploads API using the Uber class. + +from falconpy import sample_uploads as FalconUploads + +# #Grab our config parameters +with open('config.json', 'r') as file_config: + config = json.loads(file_config.read()) + +falcon = FalconUploads.Sample_Uploads(creds={ + "client_id": config["falcon_client_id"], + "client_secret": config["falcon_client_secret"] + } +) + +# Define our file +FILENAME = "testfile.jpg" +# Open the file for binary read, this will be our payload +PAYLOAD = open(FILENAME, 'rb').read() +# Upload the file using the Sample Uploads API, name this file "newfile.jpg" in the API +# Since we are using the Service Class, we do not need to specify the content type +response = falcon.UploadSampleV3(file_name="newfile.jpg", file_data=PAYLOAD) +# Grab the SHA256 unique identifier for the file we just uploaded +sha = response["body"]["resources"][0]["sha256"] +# Download a copy of this file, use the SHA256 ID to retrieve it +response = falcon.GetSampleV3(ids=sha) +# Save the result to a new file +open('serviceclass.jpg', 'wb').write(response) +# Delete the file from the API +response = falcon.DeleteSampleV3(ids=sha) +# Print the results of our delete command +print(json.dumps(response, indent=4)) \ No newline at end of file From bcb75a4ee3514f2b66fc5d0f5aa4659272be49cf Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 13:35:25 -0400 Subject: [PATCH 03/37] Code sample: Sample Uploads API --- samples/sample_uploads/sample_uploads.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py index fd6bfa881..726c6fc15 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads.py @@ -1,16 +1,16 @@ -# ____ _ _ _ _ _ -# / ___| __ _ _ __ ___ _ __ | | ___ | | | |_ __ | | ___ __ _ __| |___ -# \___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | | '_ \| |/ _ \ / _` |/ _` / __| -# ___) | (_| | | | | | | |_) | | __/ | |_| | |_) | | (_) | (_| | (_| \__ \ -# |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ +# ____ _ _ _ _ _ _ ____ ___ +# / ___| __ _ _ __ ___ _ __ | | ___ | | | |_ __ | | ___ __ _ __| |___ / \ | _ \_ _| +# \___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | | '_ \| |/ _ \ / _` |/ _` / __| / _ \ | |_) | | +# ___) | (_| | | | | | | |_) | | __/ | |_| | |_) | | (_) | (_| | (_| \__ \ / ___ \| __/| | +# |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ /_/ \_\_| |___| # |_| |_| - -# _ ____ ___ _____ _ -# / \ | _ \_ _| | ____|_ ____ _ _ __ ___ _ __ | | ___ ___ -# / _ \ | |_) | | | _| \ \/ / _` | '_ ` _ \| '_ \| |/ _ \/ __| -# / ___ \| __/| | | |___ > < (_| | | | | | | |_) | | __/\__ \ -# /_/ \_\_| |___| |_____/_/\_\__,_|_| |_| |_| .__/|_|\___||___/ -# |_| +# +# _____ _ +# | ____|_ ____ _ _ __ ___ _ __ | | ___ ___ +# | _| \ \/ / _` | '_ ` _ \| '_ \| |/ _ \/ __| +# | |___ > < (_| | | | | | | |_) | | __/\__ \ +# |_____/_/\_\__,_|_| |_| |_| .__/|_|\___||___/ +# |_| # # These examples show how to interact with the Sample Uploads API using # both the Uber class, and the regular Service class. From 78efe406ca12fcd79a848775cdb66d3cfd79ad40 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 13:37:33 -0400 Subject: [PATCH 04/37] Sample config.json file --- samples/config_sample.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 samples/config_sample.json diff --git a/samples/config_sample.json b/samples/config_sample.json new file mode 100644 index 000000000..51d83b291 --- /dev/null +++ b/samples/config_sample.json @@ -0,0 +1,4 @@ +{ + "falcon_client_id": "API ID GOES HERE", + "falcon_client_secret": "API SECRET GOES HERE" +} From c74cc9c331cd078ed35aebc0c53160886352169b Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:00:44 -0400 Subject: [PATCH 05/37] Documentation updates --- samples/sample_uploads/README.md | 17 ++++++++++++++++- samples/sample_uploads/sample_uploads.py | 6 ------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/samples/sample_uploads/README.md b/samples/sample_uploads/README.md index fa9138adc..c42799333 100644 --- a/samples/sample_uploads/README.md +++ b/samples/sample_uploads/README.md @@ -1 +1,16 @@ -# FalconPy usage examples \ No newline at end of file +![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png) [![Twitter URL](https://img.shields.io/twitter/url?label=Follow%20%40CrowdStrike&style=social&url=https%3A%2F%2Ftwitter.com%2FCrowdStrike)](https://twitter.com/CrowdStrike)
+ +# FalconPy usage examples +These examples are provided as a quick start for your project. + ++ [Samples by API](#samples-by-api) ++ [Suggestions](#suggestions) + +## Samples by API +These samples are categorized by API service collection. + +### Sample Uploads API ++ [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads.py) + +## Suggestions +Have a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). \ No newline at end of file diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py index 726c6fc15..3d435eb60 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads.py @@ -5,12 +5,6 @@ # |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ /_/ \_\_| |___| # |_| |_| # -# _____ _ -# | ____|_ ____ _ _ __ ___ _ __ | | ___ ___ -# | _| \ \/ / _` | '_ ` _ \| '_ \| |/ _ \/ __| -# | |___ > < (_| | | | | | | |_) | | __/\__ \ -# |_____/_/\_\__,_|_| |_| |_| .__/|_|\___||___/ -# |_| # # These examples show how to interact with the Sample Uploads API using # both the Uber class, and the regular Service class. From 6a35d1f312bb7227adc44d2ee39da540e770e429 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:04:51 -0400 Subject: [PATCH 06/37] Documentation updates --- samples/{sample_uploads => }/README.md | 9 +++++++++ samples/sample_uploads/sample_uploads.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) rename samples/{sample_uploads => }/README.md (59%) diff --git a/samples/sample_uploads/README.md b/samples/README.md similarity index 59% rename from samples/sample_uploads/README.md rename to samples/README.md index c42799333..558ada0b1 100644 --- a/samples/sample_uploads/README.md +++ b/samples/README.md @@ -3,9 +3,18 @@ # FalconPy usage examples These examples are provided as a quick start for your project. ++ [Authentication for Examples](#authentication-for-these-examples) + [Samples by API](#samples-by-api) + [Suggestions](#suggestions) +## Authentication for these Examples +In order to expedite sample delivery, we will be following a standard pattern for defining and providing credentials to the API. +This is not the only method of providing these values, and not recommended for production deployments as the config.json file is +**not encrypted**. + +In order to test these samples locally in your development environment, update the file config.json to reflect your current +development API credentials. + ## Samples by API These samples are categorized by API service collection. diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py index 3d435eb60..2589b8803 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads.py @@ -23,7 +23,7 @@ from falconpy import api_complete as FalconSDK # Grab our config parameters -with open('config.json', 'r') as file_config: +with open('../config.json', 'r') as file_config: config = json.loads(file_config.read()) # Create an instance of the Uber class From 9292c758d46edebe2c8db85970d51a8e36671d98 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:05:36 -0400 Subject: [PATCH 07/37] Documentation updates --- samples/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index 558ada0b1..d547b7be1 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,4 +1,6 @@ -![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png) [![Twitter URL](https://img.shields.io/twitter/url?label=Follow%20%40CrowdStrike&style=social&url=https%3A%2F%2Ftwitter.com%2FCrowdStrike)](https://twitter.com/CrowdStrike)
+![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png) + +[![Twitter URL](https://img.shields.io/twitter/url?label=Follow%20%40CrowdStrike&style=social&url=https%3A%2F%2Ftwitter.com%2FCrowdStrike)](https://twitter.com/CrowdStrike) # FalconPy usage examples These examples are provided as a quick start for your project. From 35eab6ded5145d98f47503292fb3a4041cb5d44e Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:07:54 -0400 Subject: [PATCH 08/37] Documentation updates --- samples/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index d547b7be1..a90df2034 100644 --- a/samples/README.md +++ b/samples/README.md @@ -24,4 +24,6 @@ These samples are categorized by API service collection. + [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads.py) ## Suggestions -Have a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). \ No newline at end of file +Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). + +Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](https://github.com/CrowdStrike/falconpy/CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file From b3a2ac87fdaa02ebadbc31a37429cdf013a9fca9 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:09:01 -0400 Subject: [PATCH 09/37] Documentation updates --- samples/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/README.md b/samples/README.md index a90df2034..b19fc5509 100644 --- a/samples/README.md +++ b/samples/README.md @@ -14,8 +14,8 @@ In order to expedite sample delivery, we will be following a standard pattern fo This is not the only method of providing these values, and not recommended for production deployments as the config.json file is **not encrypted**. -In order to test these samples locally in your development environment, update the file config.json to reflect your current -development API credentials. +In order to test these samples locally in your development environment, rename the file `config_sample.json` to `config.json` and then +update this file to reflect your current development API credentials. ## Samples by API These samples are categorized by API service collection. From f4d54975ad2df2a067a5b40b89040ba87db6db06 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:16:29 -0400 Subject: [PATCH 10/37] Documentation updates --- samples/README.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/samples/README.md b/samples/README.md index b19fc5509..406e08b18 100644 --- a/samples/README.md +++ b/samples/README.md @@ -6,7 +6,13 @@ These examples are provided as a quick start for your project. + [Authentication for Examples](#authentication-for-these-examples) -+ [Samples by API](#samples-by-api) ++ [Samples by API service collection](#samples-by-api-service-collection) + - [Detections](#detections) + - [Event Streams](#event-streams) + - [Falcon Discover](#falcon-discover) + - [Hosts](#hosts) + - [Real Time Response](#real-time-response) + - [Sample Uploads](#sample-uploads) + [Suggestions](#suggestions) ## Authentication for these Examples @@ -17,10 +23,25 @@ This is not the only method of providing these values, and not recommended for p In order to test these samples locally in your development environment, rename the file `config_sample.json` to `config.json` and then update this file to reflect your current development API credentials. -## Samples by API -These samples are categorized by API service collection. +## Samples by API service collection +These samples are categorized by API service collection. The list below will grow as more samples are planned. -### Sample Uploads API +### Detections +_Coming Soon_ + +### Event Streams +_Coming Soon_ + +### Falcon Discover +_Coming Soon_ + +### Hosts +_Coming Soon_ + +### Real Time Response +_Coming Soon_ + +### Sample Uploads + [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads.py) ## Suggestions From d0dd5e7962936e6337b0537c3f842bdf0e97e2f5 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:17:48 -0400 Subject: [PATCH 11/37] Documentation updates --- samples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index 406e08b18..97b09cccd 100644 --- a/samples/README.md +++ b/samples/README.md @@ -47,4 +47,4 @@ _Coming Soon_ ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). -Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](https://github.com/CrowdStrike/falconpy/CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file +Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](../CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file From 19ac67cfc8b32ac06f4646c1b7fe3a88baa6c2a2 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:19:05 -0400 Subject: [PATCH 12/37] Documentation updates --- samples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index 97b09cccd..7de486c94 100644 --- a/samples/README.md +++ b/samples/README.md @@ -47,4 +47,4 @@ _Coming Soon_ ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). -Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](../CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file +Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file From 086e476276bf126e993f881b9d663c0c4ac68526 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:20:06 -0400 Subject: [PATCH 13/37] Documentation updates --- samples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index 7de486c94..5e03516e8 100644 --- a/samples/README.md +++ b/samples/README.md @@ -47,4 +47,4 @@ _Coming Soon_ ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). -Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file +Have an example you've developed yourself that you'd like to share? **_Excellent!_** Please review our [contributing guidelines](/CONTRIBUTING.md) and then submit a pull request. \ No newline at end of file From 2af4478f913e46adadb3197c352a69d98282038a Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 14:20:52 -0400 Subject: [PATCH 14/37] Documentation updates --- samples/sample_uploads/sample_uploads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py index 2589b8803..5e16a4da1 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads.py @@ -88,4 +88,4 @@ # Delete the file from the API response = falcon.DeleteSampleV3(ids=sha) # Print the results of our delete command -print(json.dumps(response, indent=4)) \ No newline at end of file +print(json.dumps(response, indent=4)) From f750d7b0d55a7efc49ae244100f1d53f4e9428f8 Mon Sep 17 00:00:00 2001 From: Shane Shellenbarger Date: Fri, 2 Apr 2021 14:52:05 -0400 Subject: [PATCH 15/37] Uploading simple example of containing and uncontaining a host via API --- .../real_time_response/quarantine_hosts.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 samples/real_time_response/quarantine_hosts.py diff --git a/samples/real_time_response/quarantine_hosts.py b/samples/real_time_response/quarantine_hosts.py new file mode 100644 index 000000000..3aca4a55d --- /dev/null +++ b/samples/real_time_response/quarantine_hosts.py @@ -0,0 +1,79 @@ +import json +import argparse +from pprint import pprint +from falconpy import oauth2 as FalconAuth +from falconpy import hosts as FalconHosts + +parser = argparse.ArgumentParser( + "Script that leverages Falcon API to (un)contain hosts") +parser.add_argument('-c', '--creds_file', dest='creds_file', + help='Path to creds json file', required=True) +parser.add_argument('-H', '--hostname', dest='hostname', + help='Hostname to quarantine', required=True) +parser.add_argument('-l', '--lift', dest='lift_containment', + action="store_true", help='Lift containment', + default=False) + +args = parser.parse_args() + +hostname = args.hostname +if args.lift_containment: + action = "lift_containment" +else: + action = "contain" + +creds_file = args.creds_file +with open(creds_file) as f: + creds = json.load(f) + +authorization = FalconAuth.OAuth2(creds={ + "client_id": creds['falcon_client_id'], + "client_secret": creds['falcon_client_secret'] + }) + +try: + token = authorization.token()['body']['access_token'] +except Exception as e: + print("Failed to authenticate") + print(e) + exit(-1) + +if token: + falcon = FalconHosts.Hosts(access_token=token) + + PARAMS = { + 'offset': 0, + 'limit': 10, + 'filter': f"hostname:'{hostname}'" + } + + response = falcon.QueryDevicesByFilter(parameters=PARAMS) + + contain_ids = response['body']['resources'] + print() + pprint(response) + if not contain_ids: + print(f"[-] Could not find hostname: {hostname} - Please verify \ + proper case") + exit(-2) + + PARAMS = { + 'action_name': action + } + + BODY = { + 'ids': contain_ids + } + + if action == "contain": + print(f"[+] Containing: {hostname}") + else: + print(f"[+] Lifting Containment: {hostname}") + + print() + + # TODO: Get rid of action_name="contain" once bug is resolved + # BUG: https://github.com/CrowdStrike/falconpy/issues/114 + response = falcon.PerformActionV2(parameters=PARAMS, body=BODY, + action_name="contain") + pprint(response) From bbd9017e657e73d45b5e7e3971c9b8324dadf954 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 15:29:21 -0400 Subject: [PATCH 16/37] Documentation updates --- samples/README.md | 2 +- samples/real_time_response/quarantine_hosts.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index 5e03516e8..1780aad6d 100644 --- a/samples/README.md +++ b/samples/README.md @@ -39,7 +39,7 @@ _Coming Soon_ _Coming Soon_ ### Real Time Response -_Coming Soon_ ++ [Quarantine a host](real_time_response/quarantine_hosts.py) ### Sample Uploads + [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads.py) diff --git a/samples/real_time_response/quarantine_hosts.py b/samples/real_time_response/quarantine_hosts.py index 3aca4a55d..32c7c9926 100644 --- a/samples/real_time_response/quarantine_hosts.py +++ b/samples/real_time_response/quarantine_hosts.py @@ -1,3 +1,13 @@ +# ____ _ _____ _ ____ +# | _ \ ___ __ _| | |_ _(_)_ __ ___ ___ | _ \ ___ ___ _ __ ___ _ __ ___ ___ +# | |_) / _ \/ _` | | | | | | '_ ` _ \ / _ \ | |_) / _ \/ __| '_ \ / _ \| '_ \/ __|/ _ \ +# | _ < __/ (_| | | | | | | | | | | | __/ | _ < __/\__ \ |_) | (_) | | | \__ \ __/ +# |_| \_\___|\__,_|_| |_| |_|_| |_| |_|\___| |_| \_\___||___/ .__/ \___/|_| |_|___/\___| +# |_| +# +# This example demonstrates how to apply or lift containment on a host using it's hostname. +# + import json import argparse from pprint import pprint From 72b237e02d1da917d4d1db1335504cdd6a4b726b Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 15:33:24 -0400 Subject: [PATCH 17/37] Documentation updates --- samples/real_time_response/quarantine_hosts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/real_time_response/quarantine_hosts.py b/samples/real_time_response/quarantine_hosts.py index 32c7c9926..674df174c 100644 --- a/samples/real_time_response/quarantine_hosts.py +++ b/samples/real_time_response/quarantine_hosts.py @@ -5,7 +5,7 @@ # |_| \_\___|\__,_|_| |_| |_|_| |_| |_|\___| |_| \_\___||___/ .__/ \___/|_| |_|___/\___| # |_| # -# This example demonstrates how to apply or lift containment on a host using it's hostname. +# This example demonstrates how to apply or lift containment on a host using its hostname. # import json From 5d44b68f2a20a8343a79a197a141b5f7b355eaa8 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 2 Apr 2021 15:47:32 -0400 Subject: [PATCH 18/37] Documentation updates --- samples/sample_uploads/sample_uploads.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py index 5e16a4da1..b413570b0 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads.py @@ -1,8 +1,8 @@ -# ____ _ _ _ _ _ _ ____ ___ -# / ___| __ _ _ __ ___ _ __ | | ___ | | | |_ __ | | ___ __ _ __| |___ / \ | _ \_ _| -# \___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | | '_ \| |/ _ \ / _` |/ _` / __| / _ \ | |_) | | -# ___) | (_| | | | | | | |_) | | __/ | |_| | |_) | | (_) | (_| | (_| \__ \ / ___ \| __/| | -# |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ /_/ \_\_| |___| +# ____ _ _ _ _ _ +# / ___| __ _ _ __ ___ _ __ | | ___ | | | |_ __ | | ___ __ _ __| |___ +# \___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | | '_ \| |/ _ \ / _` |/ _` / __| +# ___) | (_| | | | | | | |_) | | __/ | |_| | |_) | | (_) | (_| | (_| \__ \ +# |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ # |_| |_| # # @@ -58,7 +58,7 @@ # |____/ \___|_| \_/ |_|\___\___| \____|_|\__,_|___/___/ # # This example shows how to interact with the -# Sample Uploads API using the Uber class. +# Sample Uploads API using the Sample Uploads Service class. from falconpy import sample_uploads as FalconUploads From aaa7412618a578fdfd09bae85937f25705a2d4e4 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Fri, 2 Apr 2021 21:24:06 -0400 Subject: [PATCH 19/37] Update labeler.yml Added code samples label tagging --- .github/labeler.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 81b27792f..02da9c067 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,5 +1,8 @@ documentation: -- any: ['docs/*', '*.md'] +- docs/* +- *.md +- src/falconpy/*.md +- samples/*.md package: - src/*.py @@ -13,3 +16,8 @@ pipeline: unit testing: - any: ['tests/*', 'util/*'] + +code samples: +- samples/*.py +- samples/real_time_response/*.py +- samples/sample_uploads/*.py From d9b8f02f8cf1350606f9d55e23e7038342ca1e30 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Fri, 2 Apr 2021 21:28:49 -0400 Subject: [PATCH 20/37] Update labeler.yml --- .github/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 02da9c067..91b59873e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,6 +1,6 @@ documentation: - docs/* -- *.md +- '*.md' - src/falconpy/*.md - samples/*.md From 660ffa04130cc514b113da74658c980a79328074 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Fri, 2 Apr 2021 22:07:31 -0400 Subject: [PATCH 21/37] Update wordlist.txt --- .github/wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/wordlist.txt b/.github/wordlist.txt index df98fac65..c14c785d5 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -19,6 +19,7 @@ autogenerated pytest ipython dev +config cov FalconDebug Uber From ea336d2ef72565fd858a20485bc219f60b4d0eb1 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Apr 2021 01:04:55 -0400 Subject: [PATCH 22/37] Linting --- .../real_time_response/quarantine_hosts.py | 64 ++++++++++--------- samples/sample_uploads/sample_uploads.py | 4 +- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/samples/real_time_response/quarantine_hosts.py b/samples/real_time_response/quarantine_hosts.py index 674df174c..f9e5a69ab 100644 --- a/samples/real_time_response/quarantine_hosts.py +++ b/samples/real_time_response/quarantine_hosts.py @@ -6,84 +6,86 @@ # |_| # # This example demonstrates how to apply or lift containment on a host using its hostname. +# This solution makes use of Service Class legacy authentication. # - import json import argparse -from pprint import pprint +# Import necessary FalconPy classes from falconpy import oauth2 as FalconAuth from falconpy import hosts as FalconHosts -parser = argparse.ArgumentParser( - "Script that leverages Falcon API to (un)contain hosts") -parser.add_argument('-c', '--creds_file', dest='creds_file', - help='Path to creds json file', required=True) -parser.add_argument('-H', '--hostname', dest='hostname', - help='Hostname to quarantine', required=True) -parser.add_argument('-l', '--lift', dest='lift_containment', - action="store_true", help='Lift containment', - default=False) - +# Setup our argument parser +parser = argparse.ArgumentParser("Script that leverages Falcon API to (un)contain hosts") +parser.add_argument('-c', '--creds_file', dest='creds_file', help='Path to creds json file', required=True) +parser.add_argument('-H', '--hostname', dest='hostname', help='Hostname to quarantine', required=True) +parser.add_argument('-l', '--lift', dest='lift_containment', action="store_true", help='Lift containment', default=False) +# Parse our ingested arguments args = parser.parse_args() - +# Hostname of the machine to contain / release hostname = args.hostname +# Default action is to quarantine if args.lift_containment: action = "lift_containment" else: action = "contain" - +# Use the credentials file provided creds_file = args.creds_file +# Load the contents of the creds file into the creds dictionary with open(creds_file) as f: creds = json.load(f) - +# Create an instance of our OAuth2 authorization class using our ingested creds authorization = FalconAuth.OAuth2(creds={ "client_id": creds['falcon_client_id'], "client_secret": creds['falcon_client_secret'] }) - +# Try to generate a token try: token = authorization.token()['body']['access_token'] except Exception as e: + # Exit out on authentication errors print("Failed to authenticate") print(e) exit(-1) - +# If we have a token, proceed to the next step if token: + # Create an instance of the Hosts class falcon = FalconHosts.Hosts(access_token=token) - + # Create our parameter payload, using our ingested hostname as a filter PARAMS = { 'offset': 0, 'limit': 10, 'filter': f"hostname:'{hostname}'" } - + # Query the Hosts API for hosts that match our filter pattern response = falcon.QueryDevicesByFilter(parameters=PARAMS) - + # Retrieve the list of IDs returned contain_ids = response['body']['resources'] - print() - pprint(response) + # Output the result + print(json.dumps(response, indent=4)) + if not contain_ids: - print(f"[-] Could not find hostname: {hostname} - Please verify \ - proper case") + # No hosts were found, exit out + print(f"[-] Could not find hostname: {hostname} - Please verify proper case") exit(-2) + # Create our next payload based upon the action requested PARAMS = { 'action_name': action } - + # Our body payload will contain our list of IDs BODY = { 'ids': contain_ids } - + # Provide a status update to the terminal if action == "contain": - print(f"[+] Containing: {hostname}") + print(f"\n[+] Containing: {hostname}\n") else: - print(f"[+] Lifting Containment: {hostname}") - - print() + print(f"\n[+] Lifting Containment: {hostname}\n") + # Perform the requested action # TODO: Get rid of action_name="contain" once bug is resolved # BUG: https://github.com/CrowdStrike/falconpy/issues/114 response = falcon.PerformActionV2(parameters=PARAMS, body=BODY, action_name="contain") - pprint(response) + # Output the result + print(json.dumps(response, indent=4)) diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads.py index b413570b0..e9c2b0975 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads.py @@ -17,7 +17,7 @@ # | |_| | |_) | __/ | | |___| | (_| \__ \__ \ # \___/|_.__/ \___|_| \____|_|\__,_|___/___/ # -# This example shows how to interact with the +# This example shows how to interact with the # Sample Uploads API using the Uber class. from falconpy import api_complete as FalconSDK @@ -57,7 +57,7 @@ # ___) | __/ | \ V /| | (_| __/ | |___| | (_| \__ \__ \ # |____/ \___|_| \_/ |_|\___\___| \____|_|\__,_|___/___/ # -# This example shows how to interact with the +# This example shows how to interact with the # Sample Uploads API using the Sample Uploads Service class. from falconpy import sample_uploads as FalconUploads From 6149b510b0265fd196dea3eb76bafa18a9fd02b3 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sat, 3 Apr 2021 01:09:08 -0400 Subject: [PATCH 23/37] Update linting.yml --- .github/workflows/linting.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 86e028349..f3b879aeb 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -23,10 +23,16 @@ jobs: python -m pip install --upgrade pip python -m pip install flake8 pip install -r requirements.txt - - name: Lint with flake8 + - name: Lint package source with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 src/falconpy --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide # Stop the build on all linting errors - 04.02.21 / jshcodes@CrowdStrike flake8 src/falconpy --count --max-complexity=15 --max-line-length=127 --statistics + - name: Lint samples with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 samples --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 samples --exit-zero --count --max-complexity=15 --max-line-length=127 --statistics From 301dc98405d1489b0bb7e6d946f6c6b4dacf5861 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Apr 2021 01:14:23 -0400 Subject: [PATCH 24/37] Sample Uploads sample adjustments --- samples/README.md | 3 +- ...e_uploads.py => sample_uploads_service.py} | 55 ++----------------- samples/sample_uploads/sample_uploads_uber.py | 47 ++++++++++++++++ 3 files changed, 55 insertions(+), 50 deletions(-) rename samples/sample_uploads/{sample_uploads.py => sample_uploads_service.py} (53%) create mode 100644 samples/sample_uploads/sample_uploads_uber.py diff --git a/samples/README.md b/samples/README.md index 1780aad6d..c58534d59 100644 --- a/samples/README.md +++ b/samples/README.md @@ -42,7 +42,8 @@ _Coming Soon_ + [Quarantine a host](real_time_response/quarantine_hosts.py) ### Sample Uploads -+ [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads.py) ++ [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py) ++ [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py) ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). diff --git a/samples/sample_uploads/sample_uploads.py b/samples/sample_uploads/sample_uploads_service.py similarity index 53% rename from samples/sample_uploads/sample_uploads.py rename to samples/sample_uploads/sample_uploads_service.py index e9c2b0975..134d38fed 100644 --- a/samples/sample_uploads/sample_uploads.py +++ b/samples/sample_uploads/sample_uploads_service.py @@ -5,61 +5,18 @@ # |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ # |_| |_| # -# -# These examples show how to interact with the Sample Uploads API using -# both the Uber class, and the regular Service class. -# -import json - -# _ _ _ ____ _ -# | | | | |__ ___ _ __ / ___| | __ _ ___ ___ -# | | | | '_ \ / _ \ '__| | | | |/ _` / __/ __| -# | |_| | |_) | __/ | | |___| | (_| \__ \__ \ -# \___/|_.__/ \___|_| \____|_|\__,_|___/___/ -# -# This example shows how to interact with the -# Sample Uploads API using the Uber class. - -from falconpy import api_complete as FalconSDK - -# Grab our config parameters -with open('../config.json', 'r') as file_config: - config = json.loads(file_config.read()) - -# Create an instance of the Uber class -falcon = FalconSDK.APIHarness(creds={ - "client_id": config["falcon_client_id"], - "client_secret": config["falcon_client_secret"] - } -) - -# Define our file -FILENAME = "testfile.jpg" -# Open the file for binary read, this will be our payload -PAYLOAD = open(FILENAME, 'rb').read() -# Upload the file using the Sample Uploads API, name this file "newfile.jpg" in the API -response = falcon.command('UploadSampleV3', file_name="newfile.jpg", data=PAYLOAD, content_type="application/octet-stream") -# Grab the SHA256 unique identifier for the file we just uploaded -sha = response["body"]["resources"][0]["sha256"] -# Download a copy of this file, use the SHA256 ID to retrieve it -response = falcon.command("GetSampleV3", ids=sha) -# Save the result to a new file -open('uberclass.jpg', 'wb').write(response) -# Delete the file from the API -response = falcon.command("DeleteSampleV3", ids=sha) -# Print the results of our delete command -print(json.dumps(response, indent=4)) - - # ____ _ ____ _ # / ___| ___ _ ____ _(_) ___ ___ / ___| | __ _ ___ ___ # \___ \ / _ \ '__\ \ / / |/ __/ _ \ | | | |/ _` / __/ __| # ___) | __/ | \ V /| | (_| __/ | |___| | (_| \__ \__ \ # |____/ \___|_| \_/ |_|\___\___| \____|_|\__,_|___/___/ # -# This example shows how to interact with the -# Sample Uploads API using the Sample Uploads Service class. - +# +# These examples show how to interact with the Sample Uploads API using the Service Class +# This example uses Credential authentication and supports token refresh / authentication free usage. +# +import json +# Import the Sample Uploads service class from falconpy import sample_uploads as FalconUploads # #Grab our config parameters diff --git a/samples/sample_uploads/sample_uploads_uber.py b/samples/sample_uploads/sample_uploads_uber.py new file mode 100644 index 000000000..794c70083 --- /dev/null +++ b/samples/sample_uploads/sample_uploads_uber.py @@ -0,0 +1,47 @@ +# ____ _ _ _ _ _ +# / ___| __ _ _ __ ___ _ __ | | ___ | | | |_ __ | | ___ __ _ __| |___ +# \___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | | '_ \| |/ _ \ / _` |/ _` / __| +# ___) | (_| | | | | | | |_) | | __/ | |_| | |_) | | (_) | (_| | (_| \__ \ +# |____/ \__,_|_| |_| |_| .__/|_|\___| \___/| .__/|_|\___/ \__,_|\__,_|___/ +# |_| |_| +# +# +# _ _ _ ____ _ +# | | | | |__ ___ _ __ / ___| | __ _ ___ ___ +# | | | | '_ \ / _ \ '__| | | | |/ _` / __/ __| +# | |_| | |_) | __/ | | |___| | (_| \__ \__ \ +# \___/|_.__/ \___|_| \____|_|\__,_|___/___/ +# +# These examples show how to interact with the Sample Uploads API using the Uber class. +# +import json +# Import the Uber Class +from falconpy import api_complete as FalconSDK + +# Grab our config parameters +with open('../config.json', 'r') as file_config: + config = json.loads(file_config.read()) + +# Create an instance of the Uber class +falcon = FalconSDK.APIHarness(creds={ + "client_id": config["falcon_client_id"], + "client_secret": config["falcon_client_secret"] + } +) + +# Define our file +FILENAME = "testfile.jpg" +# Open the file for binary read, this will be our payload +PAYLOAD = open(FILENAME, 'rb').read() +# Upload the file using the Sample Uploads API, name this file "newfile.jpg" in the API +response = falcon.command('UploadSampleV3', file_name="newfile.jpg", data=PAYLOAD, content_type="application/octet-stream") +# Grab the SHA256 unique identifier for the file we just uploaded +sha = response["body"]["resources"][0]["sha256"] +# Download a copy of this file, use the SHA256 ID to retrieve it +response = falcon.command("GetSampleV3", ids=sha) +# Save the result to a new file +open('uberclass.jpg', 'wb').write(response) +# Delete the file from the API +response = falcon.command("DeleteSampleV3", ids=sha) +# Print the results of our delete command +print(json.dumps(response, indent=4)) From cb1f683df006ef735cac723e8680e881153a52b3 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Apr 2021 01:17:12 -0400 Subject: [PATCH 25/37] Added samples to bandit analysis --- .github/workflows/bandit.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index ab16033fb..6ee7989a7 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -23,6 +23,9 @@ jobs: python -m pip install --upgrade pip python -m pip install bandit pip install -r requirements.txt - - name: Analyze with bandit + - name: Analyze package with bandit run: | bandit -r src + - name: Analyze samples with bandit + run: | + bandit -r samples From 87d206ffa51c4afe5ccded0c9c488b949f81f105 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sat, 3 Apr 2021 01:18:52 -0400 Subject: [PATCH 26/37] Update bandit.yml --- .github/workflows/bandit.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index ab16033fb..6ee7989a7 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -23,6 +23,9 @@ jobs: python -m pip install --upgrade pip python -m pip install bandit pip install -r requirements.txt - - name: Analyze with bandit + - name: Analyze package with bandit run: | bandit -r src + - name: Analyze samples with bandit + run: | + bandit -r samples From 51d8d03b61c9a2faf0abbc16ae89dc3fd284436e Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Apr 2021 02:00:48 -0400 Subject: [PATCH 27/37] Documentation updates --- samples/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/samples/README.md b/samples/README.md index c58534d59..a199eaabb 100644 --- a/samples/README.md +++ b/samples/README.md @@ -39,11 +39,15 @@ _Coming Soon_ _Coming Soon_ ### Real Time Response -+ [Quarantine a host](real_time_response/quarantine_hosts.py) +| Service Class | Uber Class | +| :--- | :--- | +| [Quarantine a host](real_time_response/quarantine_hosts.py) | | ### Sample Uploads -+ [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py) -+ [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py) +| Service Class | Uber Class | +| :--- | :--- | +| [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py) +| [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py) | ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). From ed4e3f09325e948cfb6d5e7132308ff97f70a075 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Apr 2021 02:01:25 -0400 Subject: [PATCH 28/37] Documentation updates --- samples/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/README.md b/samples/README.md index a199eaabb..cdc69d691 100644 --- a/samples/README.md +++ b/samples/README.md @@ -46,8 +46,7 @@ _Coming Soon_ ### Sample Uploads | Service Class | Uber Class | | :--- | :--- | -| [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py) -| [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py) | +| [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py) | [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py) | ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). From b13be0516b1e029430ae192cbdaac008983d8dea Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Apr 2021 02:01:52 -0400 Subject: [PATCH 29/37] Documentation updates --- samples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index cdc69d691..f77f68ded 100644 --- a/samples/README.md +++ b/samples/README.md @@ -46,7 +46,7 @@ _Coming Soon_ ### Sample Uploads | Service Class | Uber Class | | :--- | :--- | -| [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py) | [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py) | +| [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads_service.py) | [Upload, Retrieve and then Delete a file](sample_uploads/sample_uploads_uber.py) | ## Suggestions Got a suggestion for an example you'd like to see? Let us know by posting a message to our [discussion board](https://github.com/CrowdStrike/falconpy/discussions). From 7778d56752c633f508bcafde135e44f041fbcbf8 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sun, 4 Apr 2021 03:27:37 -0400 Subject: [PATCH 30/37] Falcon Discover example --- samples/README.md | 4 +- .../manage_discover_accounts_service.py | 251 ++++++++++++++++++ .../manage_discover_accounts_uber.py | 248 +++++++++++++++++ 3 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 samples/discover_aws/manage_discover_accounts_service.py create mode 100644 samples/discover_aws/manage_discover_accounts_uber.py diff --git a/samples/README.md b/samples/README.md index f77f68ded..aba4d6e1f 100644 --- a/samples/README.md +++ b/samples/README.md @@ -33,7 +33,9 @@ _Coming Soon_ _Coming Soon_ ### Falcon Discover -_Coming Soon_ +| Service Class | Uber Class | +| :--- | :--- | +| [Register, delete, update and check accounts](discover_aws/manage_discover_accounts_service.py) | [Register, delete, update and check accounts](discover_aws/manage_discover_accounts_uber.py) | ### Hosts _Coming Soon_ diff --git a/samples/discover_aws/manage_discover_accounts_service.py b/samples/discover_aws/manage_discover_accounts_service.py new file mode 100644 index 000000000..482454532 --- /dev/null +++ b/samples/discover_aws/manage_discover_accounts_service.py @@ -0,0 +1,251 @@ +# ____ _ _ ____ _ ___ ______ +# / ___| | ___ _ _ __| | / ___|___ _ __ _ __ ___ ___| |_ / \ \ / / ___| +# | | | |/ _ \| | | |/ _` | | | / _ \| '_ \| '_ \ / _ \/ __| __| / _ \ \ /\ / /\___ \ +# | |___| | (_) | |_| | (_| | | |__| (_) | | | | | | | __/ (__| |_ / ___ \ V V / ___) | +# \____|_|\___/ \__,_|\__,_| \____\___/|_| |_|_| |_|\___|\___|\__| /_/ \_\_/\_/ |____/ +# +# This is a modified version of the script falcon_discover_accounts, a troubleshooting script +# posted to our Cloud-AWS repository at https://github.com/CrowdStrike/Cloud-AWS. +# +# This solution demonstrates accepting user input to query the CrowdStrike Falcon Discover API +# to register, update and delete AWS accounts. An additional check function loops through all +# accounts registered, and returns configuration detail to assist with troubleshooting setup. +# +# This example leverages the Cloud Connect AWS Service Class. +# +import argparse +import json +import sys +# Falcon SDK - Cloud_Connect_AWS and OAuth2 API service classes +from falconpy import cloud_connect_aws as FalconAWS +from falconpy import oauth2 as FalconAuth + + +# =============== FORMAT API PAYLOAD +def format_api_payload(rate_limit_reqs=0, rate_limit_time=0): + # Generates a properly formatted JSON payload for POST and PATCH requests + data = { + "resources": [ + { + "cloudtrail_bucket_owner_id": cloudtrail_bucket_owner_id, + "cloudtrail_bucket_region": cloudtrail_bucket_region, + "external_id": external_id, + "iam_role_arn": iam_role_arn, + "id": local_account, + "rate_limit_reqs": rate_limit_reqs, + "rate_limit_time": rate_limit_time + } + ] + } + return data + + +# =============== ACCOUNT VALUE +def account_value(id, val, accts): + # Returns the specified value for a specific account id within account_list + returned = False + for item in accts: + if item["id"] == id: + returned = item[val] + return returned + + +# =============== CHECK ACCOUNTS +def check_account(): + # Retrieve the account list + account_list = falcon_discover.QueryAWSAccounts(parameters={"limit": f"{str(query_limit)}"})["body"]["resources"] + # Log the results of the account query to a file if logging is enabled + if log_enabled: + with open('falcon-discover-accounts.json', 'w+') as f: + json.dump(account_list, f) + # Create a list of our account IDs out of account_list + id_items = [] + for z in account_list: + id_items.append(z["id"]) + q_max = 10 # VerifyAWSAccountAccess has a ID max count of 10 + for index in range(0, len(id_items), q_max): + sub_acct_list = id_items[index:index + q_max] + temp_list = ",".join([a for a in sub_acct_list]) + access_response = falcon_discover.VerifyAWSAccountAccess(ids=temp_list) + if access_response['status_code'] == 200: + # Loop through each ID we verified + for result in access_response["body"]["resources"]: + if result["successful"]: + # This account is correctly configured + print(f'Account {result["id"]} is ok!') + else: + # This account is incorrectly configured. We'll use our account_value function to + # retrieve configuration values from the account list we've already ingested. + account_values_to_check = { + 'id': result["id"], + 'iam_role_arn': account_value(result["id"], "iam_role_arn", account_list), + 'external_id': account_value(result["id"], "external_id", account_list), + 'cloudtrail_bucket_owner_id': account_value(result["id"], "cloudtrail_bucket_owner_id", account_list), + 'cloudtrail_bucket_region': account_value(result["id"], "cloudtrail_bucket_region", account_list), + } + # Use the account_value function to retrieve the access_health branch, + # which contains our api failure reason. + try: + print('Account {} has a problem: {}'.format(result["id"], + account_value(result["id"], + "access_health", + account_list + )["api"]["reason"] + )) + except Exception: + # The above call will produce an error if we're running + # check immediately after registering an account as + # the access_health branch hasn't been populated yet. + # Requery the API for the account_list when this happens. + account_list = falcon_discover.QueryAWSAccounts( + parameters={"limit": f"{str(query_limit)}"} + )["body"]["resources"] + print('Account {} has a problem: {}'.format(result["id"], + account_value(result["id"], + "access_health", + account_list + )["api"]["reason"] + )) + # Output the account details to the user to assist with troubleshooting the account + print(f'Current settings {json.dumps(account_values_to_check, indent=4)}\n') + else: + try: + # An error has occurred + print("Got response error code {} message {}".format(access_response["status_code"], + access_response["body"]["errors"][0]["message"] + )) + except Exception: + # Handle any egregious errors that break our return error payload + print("Got response error code {} message {}".format(access_response["status_code"], access_response["body"])) + return + + +# =============== REGISTER ACCOUNT +def register_account(): + # Call the API to update the requested account. + register_response = falcon_discover.ProvisionAWSAccounts(parameters={}, body=format_api_payload()) + if register_response["status_code"] == 201: + print("Successfully registered account.") + else: + print("Registration failed with response: {} {}".format(register_response["status_code"], + register_response["body"]["errors"][0]["message"] + )) + + return + + +# =============== UPDATE ACCOUNT +def update_account(): + # Call the API to update the requested account. + update_response = falcon_discover.UpdateAWSAccounts(body=format_api_payload()) + if update_response["status_code"] == 200: + print("Successfully updated account.") + else: + print("Update failed with response: {} {}".format(update_response["status_code"], + update_response["body"]["errors"][0]["message"] + )) + + return + + +# =============== DELETE ACCOUNT +def delete_account(): + # Call the API to delete the requested account, multiple IDs can be deleted by passing in a comma-delimited list + delete_response = falcon_discover.DeleteAWSAccounts(ids=local_account) + if delete_response["status_code"] == 200: + print("Successfully deleted account.") + else: + print("Delete failed with response: {} {}".format(delete_response["status_code"], + delete_response["body"]["errors"][0]["message"] + )) + + return + + +# =============== MAIN +if __name__ == "__main__": + # Configure argument parsing + parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic") + # Fully optional + parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False) + parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true") + # Optionally required + parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted', + required=False) + parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted', + required=False) + parser.add_argument('-a', '--local_account', help='This AWS Account', required=False) + parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False) + parser.add_argument('-i', '--iam_role_arn', + help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False) + # Always required + parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True) + parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) + parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) + args = parser.parse_args() + + # =============== SET GLOBALS + command = args.command + # Only execute our defined commands + if command.lower() in "check,update,register,delete": + if command.lower() in "update,register": + # All fields required for update and register + if (args.cloudtrail_bucket_owner_id is None or + args.cloudtrail_bucket_region is None or + args.local_account is None or + args.external_id is None or + args.iam_role_arn is None): + parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command)) + else: + cloudtrail_bucket_region = args.cloudtrail_bucket_region + cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id + local_account = args.local_account + external_id = args.external_id + iam_role_arn = args.iam_role_arn + elif command.lower() in "delete": + # Delete only requires the local account ID + if args.local_account is None: + parser.error("The {} command requires the -l argument to also be specified.".format(command)) + else: + local_account = args.local_account + else: + parser.error("The {} command is not recognized.".format(command)) + # These globals exist for all requests + falcon_client_id = args.falcon_client_id + falcon_client_secret = args.falcon_client_secret + log_enabled = args.log_enabled + if args.query_limit is None: + query_limit = 100 + else: + query_limit = args.query_limit + + # =============== MAIN ROUTINE + # Authenticate using our provided falcon client_id and client_secret + try: + authorized = FalconAuth.OAuth2(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret}) + except Exception: + # We can't communicate with the endpoint, return a false token + authorized.token = lambda: False + # Try to retrieve a token from our authentication, returning false on failure + try: + token = authorized.token()["body"]["access_token"] + except Exception: + token = False + # Confirm the token was successfully retrieved + if token: + # Connect using our token and return an instance of the API gateway object + falcon_discover = FalconAWS.Cloud_Connect_AWS(access_token=token) + try: + # Execute the command by calling the named function + exec("{}_account()".format(command.lower())) + except Exception as e: + # Handle any previously unhandled errors + print("Command failed with error: {}.".format(str(e))) + # Discard our token before we exit + authorized.revoke(token) + else: + # Report that authentication failed and stop processing + print("Failed to retrieve authentication token.") + + # Force clean exit + sys.exit(0) diff --git a/samples/discover_aws/manage_discover_accounts_uber.py b/samples/discover_aws/manage_discover_accounts_uber.py new file mode 100644 index 000000000..344e168a3 --- /dev/null +++ b/samples/discover_aws/manage_discover_accounts_uber.py @@ -0,0 +1,248 @@ +# ____ _ _ ____ _ ___ ______ +# / ___| | ___ _ _ __| | / ___|___ _ __ _ __ ___ ___| |_ / \ \ / / ___| +# | | | |/ _ \| | | |/ _` | | | / _ \| '_ \| '_ \ / _ \/ __| __| / _ \ \ /\ / /\___ \ +# | |___| | (_) | |_| | (_| | | |__| (_) | | | | | | | __/ (__| |_ / ___ \ V V / ___) | +# \____|_|\___/ \__,_|\__,_| \____\___/|_| |_|_| |_|\___|\___|\__| /_/ \_\_/\_/ |____/ +# +# This is a modified version of the script falcon_discover_accounts, a troubleshooting script +# posted to our Cloud-AWS repository at https://github.com/CrowdStrike/Cloud-AWS. +# +# This solution demonstrates accepting user input to query the CrowdStrike Falcon Discover API +# to register, update and delete AWS accounts. An additional check function loops through all +# accounts registered, and returns configuration detail to assist with troubleshooting setup. +# +# This example leverages the Uber Class. +# +import argparse +import json +import sys +# Falcon SDK - All in one uber-class +from falconpy import api_complete as FalconSDK + + +# =================== FORMAT API PAYLOAD +def format_api_payload(rate_limit_reqs=0, rate_limit_time=0): + # Generates a properly formatted JSON payload for POST and PATCH requests + data = { + "resources": [ + { + "cloudtrail_bucket_owner_id": cloudtrail_bucket_owner_id, + "cloudtrail_bucket_region": cloudtrail_bucket_region, + "external_id": external_id, + "iam_role_arn": iam_role_arn, + "id": local_account, + "rate_limit_reqs": rate_limit_reqs, + "rate_limit_time": rate_limit_time + } + ] + } + return data + + +# =============== ACCOUNT VALUE +def account_value(id, val, accts): + # Returns the specified value for a specific account id within account_list + returned = False + for item in accts: + if item["id"] == id: + returned = item[val] + return returned + + +# =================== CHECK ACCOUNTS +def check_account(): + # Retrieve the account list + account_list = falcon.command(action="QueryAWSAccounts", + parameters={"limit": "{}".format(str(query_limit))} + )["body"]["resources"] + # Log the results of the account query to a file if logging is enabled + if log_enabled: + with open('falcon-discover-accounts.json', 'w+') as f: + json.dump(account_list, f) + # Create a list of our account IDs out of account_list + id_items = [] + for z in account_list: + id_items.append(z["id"]) + + q_max = 10 # VerifyAWSAccountAccess has a ID max count of 10 + for index in range(0, len(id_items), q_max): + sub_acct_list = id_items[index:index + q_max] + temp_list = ",".join([a for a in sub_acct_list]) + # Check our AWS account access against the list of accounts returned in our query + access_response = falcon.command(action="VerifyAWSAccountAccess", ids=temp_list) + if access_response['status_code'] == 200: + # Loop through each ID we verified + for result in access_response["body"]["resources"]: + if result["successful"]: + # This account is correctly configured + print(f'Account {result["id"]} is ok!') + else: + # This account is incorrectly configured. We'll use our account_value function to + # retrieve configuration values from the account list we've already ingested. + account_values_to_check = { + 'id': result["id"], + 'iam_role_arn': account_value(result["id"], "iam_role_arn", account_list), + 'external_id': account_value(result["id"], "external_id", account_list), + 'cloudtrail_bucket_owner_id': account_value(result["id"], "cloudtrail_bucket_owner_id", account_list), + 'cloudtrail_bucket_region': account_value(result["id"], "cloudtrail_bucket_region", account_list), + } + # Use the account_value function to retrieve the access_health branch, + # which contains our api failure reason + try: + print('Account {} has a problem: {}'.format(result["id"], + account_value(result["id"], + "access_health", + account_list + )["api"]["reason"] + )) + except Exception: + # The above call will produce an error if we're running + # check immediately after registering an account as + # the access_health branch hasn't been populated yet. + # Requery the API for the account_list when this happens. + account_list = falcon.command(action="QueryAWSAccounts", + parameters={"limit": "{}".format(str(query_limit))} + )["body"]["resources"] + print('Account {} has a problem: {}'.format(result["id"], + account_value(result["id"], + "access_health", + account_list + )["api"]["reason"] + )) + # Output the account details to the user to assist with troubleshooting the account + print(f'Current settings {json.dumps(account_values_to_check, indent=4)}\n') + else: + try: + # An error has occurred + print("Got response error code {} message {}".format(access_response["status_code"], + access_response["body"]["errors"][0]["message"] + )) + except Exception: + # Handle any egregious errors that break our return error payload + print("Got response error code {} message {}".format(access_response["status_code"], access_response["body"])) + + return + + +# =================== REGISTER ACCOUNT +def register_account(): + # Call the API to update the requested account. + register_response = falcon.command(action="ProvisionAWSAccounts", parameters={}, body=format_api_payload()) + if register_response["status_code"] == 201: + print("Successfully registered account.") + else: + print("Registration failed with response: {} {}".format(register_response["status_code"], + register_response["body"]["errors"][0]["message"] + )) + + return + + +# =================== UPDATE ACCOUNT +def update_account(): + # Call the API to update the requested account. + update_response = falcon.command(action="UpdateAWSAccounts", body=format_api_payload()) + if update_response["status_code"] == 200: + print("Successfully updated account.") + else: + print("Update failed with response: {} {}".format(update_response["status_code"], + update_response["body"]["errors"][0]["message"] + )) + + return + + +# =================== DELETE ACCOUNT +def delete_account(): + # Call the API to delete the requested account, multiple IDs can be deleted by passing in a comma-delimited list + delete_response = falcon.command(action="DeleteAWSAccounts", parameters={}, ids=local_account) + if delete_response["status_code"] == 200: + print("Successfully deleted account.") + else: + print("Delete failed with response: {} {}".format(delete_response["status_code"], + delete_response["body"]["errors"][0]["message"] + )) + + return + + +# =================== MAIN +if __name__ == "__main__": + # Configure argument parsing + parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic") + # Fully optional + parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False) + parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true") + # Optionally required + parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted', + required=False) + parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted', + required=False) + parser.add_argument('-a', '--local_account', help='This AWS Account', required=False) + parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False) + parser.add_argument('-i', '--iam_role_arn', + help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False) + # Always required + parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True) + parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) + parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) + args = parser.parse_args() + + # =================== SET GLOBALS + command = args.command + # Only execute our defined commands + if command.lower() in "check,update,register,delete": + if command.lower() in "update,register": + # All fields required for update and register + if (args.cloudtrail_bucket_owner_id is None or + args.cloudtrail_bucket_region is None or + args.local_account is None or + args.external_id is None or + args.iam_role_arn is None): + parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command)) + else: + cloudtrail_bucket_region = args.cloudtrail_bucket_region + cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id + local_account = args.local_account + external_id = args.external_id + iam_role_arn = args.iam_role_arn + elif command.lower() in "delete": + # Delete only requires the local account ID + if args.local_account is None: + parser.error("The {} command requires the -l argument to also be specified.".format(command)) + else: + local_account = args.local_account + else: + parser.error("The {} command is not recognized.".format(command)) + # These globals exist for all requests + falcon_client_id = args.falcon_client_id + falcon_client_secret = args.falcon_client_secret + log_enabled = args.log_enabled + if args.query_limit is None: + query_limit = 100 + else: + query_limit = args.query_limit + + # =================== MAIN ROUTINE + # Connect to the API using our provided falcon client_id and client_secret + try: + falcon = FalconSDK.APIHarness(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret}) + except Exception: + # We can't communicate with the endpoint + print("Unable to communicate with API") + # Authenticate + if falcon.authenticate(): + try: + # Execute the command by calling the named function + exec("{}_account()".format(command.lower())) + except Exception as e: + # Handle any previously unhandled errors + print("Command failed with error: {}.".format(str(e))) + # Discard our token before we exit + falcon.deauthenticate() + else: + # Report that authentication failed and stop processing + print("Authentication Failure.") + + # Force clean exit + sys.exit(0) From c4dbb014a1e11c52514550b2deaafb5c3423314b Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sun, 4 Apr 2021 03:35:19 -0400 Subject: [PATCH 31/37] Update bandit.yml Bandit analysis of samples no longer stops the build --- .github/workflows/bandit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index 6ee7989a7..a78083814 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -28,4 +28,4 @@ jobs: bandit -r src - name: Analyze samples with bandit run: | - bandit -r samples + bandit -r samples --exit-zero From 70190384ade43e537d874c14e687de6d5afa1121 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sun, 4 Apr 2021 04:36:30 -0400 Subject: [PATCH 32/37] Adjustments --- .github/workflows/bandit.yml | 2 +- .../manage_discover_accounts_service.py | 177 +++++++++--------- .../manage_discover_accounts_uber.py | 161 ++++++++-------- 3 files changed, 178 insertions(+), 162 deletions(-) diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index a78083814..6ee7989a7 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -28,4 +28,4 @@ jobs: bandit -r src - name: Analyze samples with bandit run: | - bandit -r samples --exit-zero + bandit -r samples diff --git a/samples/discover_aws/manage_discover_accounts_service.py b/samples/discover_aws/manage_discover_accounts_service.py index 482454532..29dc789ca 100644 --- a/samples/discover_aws/manage_discover_accounts_service.py +++ b/samples/discover_aws/manage_discover_accounts_service.py @@ -52,8 +52,9 @@ def account_value(id, val, accts): # =============== CHECK ACCOUNTS def check_account(): + # Retrieve the account list - account_list = falcon_discover.QueryAWSAccounts(parameters={"limit": f"{str(query_limit)}"})["body"]["resources"] + account_list = falcon_discover.QueryAWSAccounts(parameters={"limit": int(query_limit)})["body"]["resources"] # Log the results of the account query to a file if logging is enabled if log_enabled: with open('falcon-discover-accounts.json', 'w+') as f: @@ -163,89 +164,97 @@ def delete_account(): # =============== MAIN -if __name__ == "__main__": - # Configure argument parsing - parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic") - # Fully optional - parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False) - parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true") - # Optionally required - parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted', - required=False) - parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted', - required=False) - parser.add_argument('-a', '--local_account', help='This AWS Account', required=False) - parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False) - parser.add_argument('-i', '--iam_role_arn', - help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False) - # Always required - parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True) - parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) - parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) - args = parser.parse_args() - - # =============== SET GLOBALS - command = args.command - # Only execute our defined commands - if command.lower() in "check,update,register,delete": - if command.lower() in "update,register": - # All fields required for update and register - if (args.cloudtrail_bucket_owner_id is None or - args.cloudtrail_bucket_region is None or - args.local_account is None or - args.external_id is None or - args.iam_role_arn is None): - parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command)) - else: - cloudtrail_bucket_region = args.cloudtrail_bucket_region - cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id - local_account = args.local_account - external_id = args.external_id - iam_role_arn = args.iam_role_arn - elif command.lower() in "delete": - # Delete only requires the local account ID - if args.local_account is None: - parser.error("The {} command requires the -l argument to also be specified.".format(command)) - else: - local_account = args.local_account - else: - parser.error("The {} command is not recognized.".format(command)) - # These globals exist for all requests - falcon_client_id = args.falcon_client_id - falcon_client_secret = args.falcon_client_secret - log_enabled = args.log_enabled - if args.query_limit is None: - query_limit = 100 - else: - query_limit = args.query_limit - # =============== MAIN ROUTINE - # Authenticate using our provided falcon client_id and client_secret - try: - authorized = FalconAuth.OAuth2(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret}) - except Exception: - # We can't communicate with the endpoint, return a false token - authorized.token = lambda: False - # Try to retrieve a token from our authentication, returning false on failure +# Configure argument parsing +parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic") +# Fully optional +parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False) +parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true") +# Optionally required +parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted', + required=False) +parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted', + required=False) +parser.add_argument('-a', '--local_account', help='This AWS Account', required=False) +parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False) +parser.add_argument('-i', '--iam_role_arn', + help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False) +# Always required +parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True) +parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) +parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) +args = parser.parse_args() + +# =============== SET GLOBALS +command = args.command +# Only execute our defined commands +if command.lower() in "check,update,register,delete": + if command.lower() in "update,register": + # All fields required for update and register + if (args.cloudtrail_bucket_owner_id is None or + args.cloudtrail_bucket_region is None or + args.local_account is None or + args.external_id is None or + args.iam_role_arn is None): + parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command)) + else: + cloudtrail_bucket_region = args.cloudtrail_bucket_region + cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id + local_account = args.local_account + external_id = args.external_id + iam_role_arn = args.iam_role_arn + elif command.lower() in "delete": + # Delete only requires the local account ID + if args.local_account is None: + parser.error("The {} command requires the -l argument to also be specified.".format(command)) + else: + local_account = args.local_account +else: + parser.error("The {} command is not recognized.".format(command)) +# These globals exist for all requests +falcon_client_id = args.falcon_client_id +falcon_client_secret = args.falcon_client_secret +log_enabled = args.log_enabled +if args.query_limit is None: + query_limit = 100 +else: + query_limit = args.query_limit + +# =============== MAIN ROUTINE +# Authenticate using our provided falcon client_id and client_secret +try: + authorized = FalconAuth.OAuth2(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret}) +except Exception: + # We can't communicate with the endpoint, return a false token + authorized.token = lambda: False +# Try to retrieve a token from our authentication, returning false on failure +try: + token = authorized.token()["body"]["access_token"] +except Exception: + token = False + +# Confirm the token was successfully retrieved +if token: + # Connect using our token and return an instance of the API gateway object + falcon_discover = FalconAWS.Cloud_Connect_AWS(access_token=token) try: - token = authorized.token()["body"]["access_token"] - except Exception: - token = False - # Confirm the token was successfully retrieved - if token: - # Connect using our token and return an instance of the API gateway object - falcon_discover = FalconAWS.Cloud_Connect_AWS(access_token=token) - try: - # Execute the command by calling the named function - exec("{}_account()".format(command.lower())) - except Exception as e: - # Handle any previously unhandled errors - print("Command failed with error: {}.".format(str(e))) - # Discard our token before we exit - authorized.revoke(token) - else: - # Report that authentication failed and stop processing - print("Failed to retrieve authentication token.") + # Execute the requested command + if command.lower() == "delete": + delete_account() + elif command.lower() == "register": + register_account() + elif command.lower() == "update": + update_account() + else: + check_account() + except Exception as e: + # Handle any previously unhandled errors + print("Command failed with error: {}.".format(str(e))) + # Discard our token before we exit + authorized.revoke(token) +else: + # Report that authentication failed and stop processing + print("Failed to retrieve authentication token.") - # Force clean exit - sys.exit(0) +# Force clean exit +sys.exit(0) diff --git a/samples/discover_aws/manage_discover_accounts_uber.py b/samples/discover_aws/manage_discover_accounts_uber.py index 344e168a3..d19e729bb 100644 --- a/samples/discover_aws/manage_discover_accounts_uber.py +++ b/samples/discover_aws/manage_discover_accounts_uber.py @@ -167,82 +167,89 @@ def delete_account(): # =================== MAIN -if __name__ == "__main__": - # Configure argument parsing - parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic") - # Fully optional - parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False) - parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true") - # Optionally required - parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted', - required=False) - parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted', - required=False) - parser.add_argument('-a', '--local_account', help='This AWS Account', required=False) - parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False) - parser.add_argument('-i', '--iam_role_arn', - help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False) - # Always required - parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True) - parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) - parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) - args = parser.parse_args() - - # =================== SET GLOBALS - command = args.command - # Only execute our defined commands - if command.lower() in "check,update,register,delete": - if command.lower() in "update,register": - # All fields required for update and register - if (args.cloudtrail_bucket_owner_id is None or - args.cloudtrail_bucket_region is None or - args.local_account is None or - args.external_id is None or - args.iam_role_arn is None): - parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command)) - else: - cloudtrail_bucket_region = args.cloudtrail_bucket_region - cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id - local_account = args.local_account - external_id = args.external_id - iam_role_arn = args.iam_role_arn - elif command.lower() in "delete": - # Delete only requires the local account ID - if args.local_account is None: - parser.error("The {} command requires the -l argument to also be specified.".format(command)) - else: - local_account = args.local_account - else: - parser.error("The {} command is not recognized.".format(command)) - # These globals exist for all requests - falcon_client_id = args.falcon_client_id - falcon_client_secret = args.falcon_client_secret - log_enabled = args.log_enabled - if args.query_limit is None: - query_limit = 100 - else: - query_limit = args.query_limit - # =================== MAIN ROUTINE - # Connect to the API using our provided falcon client_id and client_secret +# Configure argument parsing +parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic") +# Fully optional +parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False) +parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true") +# Optionally required +parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted', + required=False) +parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted', + required=False) +parser.add_argument('-a', '--local_account', help='This AWS Account', required=False) +parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False) +parser.add_argument('-i', '--iam_role_arn', + help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False) +# Always required +parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True) +parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True) +parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True) +args = parser.parse_args() + +# =================== SET GLOBALS +command = args.command +# Only execute our defined commands +if command.lower() in "check,update,register,delete": + if command.lower() in "update,register": + # All fields required for update and register + if (args.cloudtrail_bucket_owner_id is None or + args.cloudtrail_bucket_region is None or + args.local_account is None or + args.external_id is None or + args.iam_role_arn is None): + parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command)) + else: + cloudtrail_bucket_region = args.cloudtrail_bucket_region + cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id + local_account = args.local_account + external_id = args.external_id + iam_role_arn = args.iam_role_arn + elif command.lower() in "delete": + # Delete only requires the local account ID + if args.local_account is None: + parser.error("The {} command requires the -l argument to also be specified.".format(command)) + else: + local_account = args.local_account +else: + parser.error("The {} command is not recognized.".format(command)) +# These globals exist for all requests +falcon_client_id = args.falcon_client_id +falcon_client_secret = args.falcon_client_secret +log_enabled = args.log_enabled +if args.query_limit is None: + query_limit = 100 +else: + query_limit = args.query_limit + +# =================== MAIN ROUTINE +# Connect to the API using our provided falcon client_id and client_secret +try: + falcon = FalconSDK.APIHarness(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret}) +except Exception: + # We can't communicate with the endpoint + print("Unable to communicate with API") +# Authenticate +if falcon.authenticate(): try: - falcon = FalconSDK.APIHarness(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret}) - except Exception: - # We can't communicate with the endpoint - print("Unable to communicate with API") - # Authenticate - if falcon.authenticate(): - try: - # Execute the command by calling the named function - exec("{}_account()".format(command.lower())) - except Exception as e: - # Handle any previously unhandled errors - print("Command failed with error: {}.".format(str(e))) - # Discard our token before we exit - falcon.deauthenticate() - else: - # Report that authentication failed and stop processing - print("Authentication Failure.") - - # Force clean exit - sys.exit(0) + # Execute the requested command + if command.lower() == "delete": + delete_account() + elif command.lower() == "register": + register_account() + elif command.lower() == "update": + update_account() + else: + check_account() + except Exception as e: + # Handle any previously unhandled errors + print("Command failed with error: {}.".format(str(e))) + # Discard our token before we exit + falcon.deauthenticate() +else: + # Report that authentication failed and stop processing + print("Authentication Failure.") + +# Force clean exit +sys.exit(0) From fe10c3ddb85f55a1f61edf1223e7484c1b1343d9 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sun, 4 Apr 2021 04:44:30 -0400 Subject: [PATCH 33/37] Update test_uber_api_complete.py --- tests/test_uber_api_complete.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_uber_api_complete.py b/tests/test_uber_api_complete.py index 2f88347f1..8205f035f 100644 --- a/tests/test_uber_api_complete.py +++ b/tests/test_uber_api_complete.py @@ -36,7 +36,8 @@ ) falcon.authenticate() if not falcon.authenticated: - sys.exit(1) + print("Unable to authenticated, exiting unit test.") + sys.exit(0) class TestUber: From c5e5574e58fa17b4c226c3dba9abb297b5a12f4c Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sun, 4 Apr 2021 04:47:09 -0400 Subject: [PATCH 34/37] Update test_uber_api_complete.py --- tests/test_uber_api_complete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_uber_api_complete.py b/tests/test_uber_api_complete.py index 8205f035f..7357ef79e 100644 --- a/tests/test_uber_api_complete.py +++ b/tests/test_uber_api_complete.py @@ -36,7 +36,7 @@ ) falcon.authenticate() if not falcon.authenticated: - print("Unable to authenticated, exiting unit test.") + print("Unable to authenticate, exiting unit test.") sys.exit(0) From 6069722acb6aa565533c0aa9fb251acd6cb81b75 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sun, 4 Apr 2021 06:05:15 -0400 Subject: [PATCH 35/37] Update labeler.yml --- .github/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 91b59873e..348f6dfd3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -19,5 +19,6 @@ unit testing: code samples: - samples/*.py +- samples/discover_aws/*.py - samples/real_time_response/*.py - samples/sample_uploads/*.py From 7cb1d16f084e1d61f6776d6378c5581c0f134c84 Mon Sep 17 00:00:00 2001 From: jshcodes <74007258+jshcodes@users.noreply.github.com> Date: Sun, 4 Apr 2021 06:08:07 -0400 Subject: [PATCH 36/37] Update test_uber_api_complete.py --- tests/test_uber_api_complete.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_uber_api_complete.py b/tests/test_uber_api_complete.py index 7357ef79e..2f88347f1 100644 --- a/tests/test_uber_api_complete.py +++ b/tests/test_uber_api_complete.py @@ -36,8 +36,7 @@ ) falcon.authenticate() if not falcon.authenticated: - print("Unable to authenticate, exiting unit test.") - sys.exit(0) + sys.exit(1) class TestUber: From 64854868543dea3bff66b08097d91c969ebd11ec Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sun, 4 Apr 2021 06:21:31 -0400 Subject: [PATCH 37/37] Comment update --- samples/discover_aws/manage_discover_accounts_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/discover_aws/manage_discover_accounts_service.py b/samples/discover_aws/manage_discover_accounts_service.py index 29dc789ca..49a76b32d 100644 --- a/samples/discover_aws/manage_discover_accounts_service.py +++ b/samples/discover_aws/manage_discover_accounts_service.py @@ -11,7 +11,7 @@ # to register, update and delete AWS accounts. An additional check function loops through all # accounts registered, and returns configuration detail to assist with troubleshooting setup. # -# This example leverages the Cloud Connect AWS Service Class. +# This example leverages the Cloud Connect AWS Service Class and legacy authentication. # import argparse import json