Skip to content

Commit efef8ed

Browse files
jshcodesShane Shellenbarger
andauthored
Discover for AWS Usage Examples (#117)
* Update label_request.yml Labeller fails on forks, removing workflow from dev branches. * Sample code: Sample Uploads API * Code sample: Sample Uploads API * Sample config.json file * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Documentation updates * Uploading simple example of containing and uncontaining a host via API * Documentation updates * Documentation updates * Documentation updates * Update labeler.yml Added code samples label tagging * Update labeler.yml * Update wordlist.txt * Linting * Update linting.yml * Sample Uploads sample adjustments * Added samples to bandit analysis * Update bandit.yml * Documentation updates * Documentation updates * Documentation updates * Falcon Discover example * Update bandit.yml Bandit analysis of samples no longer stops the build * Adjustments * Update test_uber_api_complete.py * Update test_uber_api_complete.py * Update labeler.yml * Update test_uber_api_complete.py * Comment update Co-authored-by: Shane Shellenbarger <[email protected]>
1 parent 69ed0bc commit efef8ed

File tree

4 files changed

+526
-4
lines changed

4 files changed

+526
-4
lines changed

.github/labeler.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ unit testing:
1919

2020
code samples:
2121
- samples/*.py
22+
- samples/discover_aws/*.py
2223
- samples/real_time_response/*.py
2324
- samples/sample_uploads/*.py

samples/README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,23 @@ _Coming Soon_
3333
_Coming Soon_
3434

3535
### Falcon Discover
36-
_Coming Soon_
36+
| Service Class | Uber Class |
37+
| :--- | :--- |
38+
| [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) |
39+
3740

3841
### Hosts
3942
_Coming Soon_
4043

4144
### Real Time Response
42-
+ [Quarantine a host](real_time_response/quarantine_hosts.py)
45+
| Service Class | Uber Class |
46+
| :--- | :--- |
47+
| [Quarantine a host](real_time_response/quarantine_hosts.py) | |
4348

4449
### Sample Uploads
45-
+ [Upload, Retrieve and then Delete a file (Service Class)](sample_uploads/sample_uploads_service.py)
46-
+ [Upload, Retrieve and then Delete a file (Uber Class)](sample_uploads/sample_uploads_uber.py)
50+
| Service Class | Uber Class |
51+
| :--- | :--- |
52+
| [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) |
4753

4854
## Suggestions
4955
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).
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# ____ _ _ ____ _ ___ ______
2+
# / ___| | ___ _ _ __| | / ___|___ _ __ _ __ ___ ___| |_ / \ \ / / ___|
3+
# | | | |/ _ \| | | |/ _` | | | / _ \| '_ \| '_ \ / _ \/ __| __| / _ \ \ /\ / /\___ \
4+
# | |___| | (_) | |_| | (_| | | |__| (_) | | | | | | | __/ (__| |_ / ___ \ V V / ___) |
5+
# \____|_|\___/ \__,_|\__,_| \____\___/|_| |_|_| |_|\___|\___|\__| /_/ \_\_/\_/ |____/
6+
#
7+
# This is a modified version of the script falcon_discover_accounts, a troubleshooting script
8+
# posted to our Cloud-AWS repository at https://github.com/CrowdStrike/Cloud-AWS.
9+
#
10+
# This solution demonstrates accepting user input to query the CrowdStrike Falcon Discover API
11+
# to register, update and delete AWS accounts. An additional check function loops through all
12+
# accounts registered, and returns configuration detail to assist with troubleshooting setup.
13+
#
14+
# This example leverages the Cloud Connect AWS Service Class and legacy authentication.
15+
#
16+
import argparse
17+
import json
18+
import sys
19+
# Falcon SDK - Cloud_Connect_AWS and OAuth2 API service classes
20+
from falconpy import cloud_connect_aws as FalconAWS
21+
from falconpy import oauth2 as FalconAuth
22+
23+
24+
# =============== FORMAT API PAYLOAD
25+
def format_api_payload(rate_limit_reqs=0, rate_limit_time=0):
26+
# Generates a properly formatted JSON payload for POST and PATCH requests
27+
data = {
28+
"resources": [
29+
{
30+
"cloudtrail_bucket_owner_id": cloudtrail_bucket_owner_id,
31+
"cloudtrail_bucket_region": cloudtrail_bucket_region,
32+
"external_id": external_id,
33+
"iam_role_arn": iam_role_arn,
34+
"id": local_account,
35+
"rate_limit_reqs": rate_limit_reqs,
36+
"rate_limit_time": rate_limit_time
37+
}
38+
]
39+
}
40+
return data
41+
42+
43+
# =============== ACCOUNT VALUE
44+
def account_value(id, val, accts):
45+
# Returns the specified value for a specific account id within account_list
46+
returned = False
47+
for item in accts:
48+
if item["id"] == id:
49+
returned = item[val]
50+
return returned
51+
52+
53+
# =============== CHECK ACCOUNTS
54+
def check_account():
55+
56+
# Retrieve the account list
57+
account_list = falcon_discover.QueryAWSAccounts(parameters={"limit": int(query_limit)})["body"]["resources"]
58+
# Log the results of the account query to a file if logging is enabled
59+
if log_enabled:
60+
with open('falcon-discover-accounts.json', 'w+') as f:
61+
json.dump(account_list, f)
62+
# Create a list of our account IDs out of account_list
63+
id_items = []
64+
for z in account_list:
65+
id_items.append(z["id"])
66+
q_max = 10 # VerifyAWSAccountAccess has a ID max count of 10
67+
for index in range(0, len(id_items), q_max):
68+
sub_acct_list = id_items[index:index + q_max]
69+
temp_list = ",".join([a for a in sub_acct_list])
70+
access_response = falcon_discover.VerifyAWSAccountAccess(ids=temp_list)
71+
if access_response['status_code'] == 200:
72+
# Loop through each ID we verified
73+
for result in access_response["body"]["resources"]:
74+
if result["successful"]:
75+
# This account is correctly configured
76+
print(f'Account {result["id"]} is ok!')
77+
else:
78+
# This account is incorrectly configured. We'll use our account_value function to
79+
# retrieve configuration values from the account list we've already ingested.
80+
account_values_to_check = {
81+
'id': result["id"],
82+
'iam_role_arn': account_value(result["id"], "iam_role_arn", account_list),
83+
'external_id': account_value(result["id"], "external_id", account_list),
84+
'cloudtrail_bucket_owner_id': account_value(result["id"], "cloudtrail_bucket_owner_id", account_list),
85+
'cloudtrail_bucket_region': account_value(result["id"], "cloudtrail_bucket_region", account_list),
86+
}
87+
# Use the account_value function to retrieve the access_health branch,
88+
# which contains our api failure reason.
89+
try:
90+
print('Account {} has a problem: {}'.format(result["id"],
91+
account_value(result["id"],
92+
"access_health",
93+
account_list
94+
)["api"]["reason"]
95+
))
96+
except Exception:
97+
# The above call will produce an error if we're running
98+
# check immediately after registering an account as
99+
# the access_health branch hasn't been populated yet.
100+
# Requery the API for the account_list when this happens.
101+
account_list = falcon_discover.QueryAWSAccounts(
102+
parameters={"limit": f"{str(query_limit)}"}
103+
)["body"]["resources"]
104+
print('Account {} has a problem: {}'.format(result["id"],
105+
account_value(result["id"],
106+
"access_health",
107+
account_list
108+
)["api"]["reason"]
109+
))
110+
# Output the account details to the user to assist with troubleshooting the account
111+
print(f'Current settings {json.dumps(account_values_to_check, indent=4)}\n')
112+
else:
113+
try:
114+
# An error has occurred
115+
print("Got response error code {} message {}".format(access_response["status_code"],
116+
access_response["body"]["errors"][0]["message"]
117+
))
118+
except Exception:
119+
# Handle any egregious errors that break our return error payload
120+
print("Got response error code {} message {}".format(access_response["status_code"], access_response["body"]))
121+
return
122+
123+
124+
# =============== REGISTER ACCOUNT
125+
def register_account():
126+
# Call the API to update the requested account.
127+
register_response = falcon_discover.ProvisionAWSAccounts(parameters={}, body=format_api_payload())
128+
if register_response["status_code"] == 201:
129+
print("Successfully registered account.")
130+
else:
131+
print("Registration failed with response: {} {}".format(register_response["status_code"],
132+
register_response["body"]["errors"][0]["message"]
133+
))
134+
135+
return
136+
137+
138+
# =============== UPDATE ACCOUNT
139+
def update_account():
140+
# Call the API to update the requested account.
141+
update_response = falcon_discover.UpdateAWSAccounts(body=format_api_payload())
142+
if update_response["status_code"] == 200:
143+
print("Successfully updated account.")
144+
else:
145+
print("Update failed with response: {} {}".format(update_response["status_code"],
146+
update_response["body"]["errors"][0]["message"]
147+
))
148+
149+
return
150+
151+
152+
# =============== DELETE ACCOUNT
153+
def delete_account():
154+
# Call the API to delete the requested account, multiple IDs can be deleted by passing in a comma-delimited list
155+
delete_response = falcon_discover.DeleteAWSAccounts(ids=local_account)
156+
if delete_response["status_code"] == 200:
157+
print("Successfully deleted account.")
158+
else:
159+
print("Delete failed with response: {} {}".format(delete_response["status_code"],
160+
delete_response["body"]["errors"][0]["message"]
161+
))
162+
163+
return
164+
165+
166+
# =============== MAIN
167+
168+
# Configure argument parsing
169+
parser = argparse.ArgumentParser(description="Get Params to send notification to CRWD topic")
170+
# Fully optional
171+
parser.add_argument('-q', '--query_limit', help='The query limit used for check account commands', required=False)
172+
parser.add_argument('-l', '--log_enabled', help='Save results to a file?', required=False, action="store_true")
173+
# Optionally required
174+
parser.add_argument('-r', '--cloudtrail_bucket_region', help='AWS Region where the S3 bucket is hosted',
175+
required=False)
176+
parser.add_argument('-o', '--cloudtrail_bucket_owner_id', help='Account where the S3 bucket is hosted',
177+
required=False)
178+
parser.add_argument('-a', '--local_account', help='This AWS Account', required=False)
179+
parser.add_argument('-e', '--external_id', help='External ID used to assume role in account', required=False)
180+
parser.add_argument('-i', '--iam_role_arn',
181+
help='IAM AWS IAM Role ARN that grants access to resources for Crowdstrike', required=False)
182+
# Always required
183+
parser.add_argument('-c', '--command', help='Troubleshooting action to perform', required=True)
184+
parser.add_argument("-f", "--falcon_client_id", help="Falcon Client ID", required=True)
185+
parser.add_argument("-s", "--falcon_client_secret", help="Falcon Client Secret", required=True)
186+
args = parser.parse_args()
187+
188+
# =============== SET GLOBALS
189+
command = args.command
190+
# Only execute our defined commands
191+
if command.lower() in "check,update,register,delete":
192+
if command.lower() in "update,register":
193+
# All fields required for update and register
194+
if (args.cloudtrail_bucket_owner_id is None or
195+
args.cloudtrail_bucket_region is None or
196+
args.local_account is None or
197+
args.external_id is None or
198+
args.iam_role_arn is None):
199+
parser.error("The {} command requires the -r, -o, -a, -e, -i arguments to also be specified.".format(command))
200+
else:
201+
cloudtrail_bucket_region = args.cloudtrail_bucket_region
202+
cloudtrail_bucket_owner_id = args.cloudtrail_bucket_owner_id
203+
local_account = args.local_account
204+
external_id = args.external_id
205+
iam_role_arn = args.iam_role_arn
206+
elif command.lower() in "delete":
207+
# Delete only requires the local account ID
208+
if args.local_account is None:
209+
parser.error("The {} command requires the -l argument to also be specified.".format(command))
210+
else:
211+
local_account = args.local_account
212+
else:
213+
parser.error("The {} command is not recognized.".format(command))
214+
# These globals exist for all requests
215+
falcon_client_id = args.falcon_client_id
216+
falcon_client_secret = args.falcon_client_secret
217+
log_enabled = args.log_enabled
218+
if args.query_limit is None:
219+
query_limit = 100
220+
else:
221+
query_limit = args.query_limit
222+
223+
# =============== MAIN ROUTINE
224+
# Authenticate using our provided falcon client_id and client_secret
225+
try:
226+
authorized = FalconAuth.OAuth2(creds={'client_id': falcon_client_id, 'client_secret': falcon_client_secret})
227+
except Exception:
228+
# We can't communicate with the endpoint, return a false token
229+
authorized.token = lambda: False
230+
# Try to retrieve a token from our authentication, returning false on failure
231+
try:
232+
token = authorized.token()["body"]["access_token"]
233+
except Exception:
234+
token = False
235+
236+
# Confirm the token was successfully retrieved
237+
if token:
238+
# Connect using our token and return an instance of the API gateway object
239+
falcon_discover = FalconAWS.Cloud_Connect_AWS(access_token=token)
240+
try:
241+
# Execute the requested command
242+
if command.lower() == "delete":
243+
delete_account()
244+
elif command.lower() == "register":
245+
register_account()
246+
elif command.lower() == "update":
247+
update_account()
248+
else:
249+
check_account()
250+
except Exception as e:
251+
# Handle any previously unhandled errors
252+
print("Command failed with error: {}.".format(str(e)))
253+
# Discard our token before we exit
254+
authorized.revoke(token)
255+
else:
256+
# Report that authentication failed and stop processing
257+
print("Failed to retrieve authentication token.")
258+
259+
# Force clean exit
260+
sys.exit(0)

0 commit comments

Comments
 (0)