-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FalconPy Sample Submissions from Don-Swanson-Adobe
- Loading branch information
1 parent
ddf5520
commit 09fa49b
Showing
42 changed files
with
2,431 additions
and
0 deletions.
There are no files selected for viewing
277 changes: 277 additions & 0 deletions
277
samples/automation_lambdas/ArtifactoryInstallerUpdate.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
#This lambda automation is designed to pull down the latest Sensor Installers from the Falcon API and upload them to an Artifactory server | ||
#Please see the attached PDF for how to setup Secrets Manager and the IAM role for these Lambda function | ||
#The attached PDF Also includes instructions on how to setup and add an AWS Layer to this Lambda function to use FalconPy | ||
#Required Layers for this Function: FalconPy | ||
#You will also need to increase the Lambda Timeout to 15 minutes, the memory to 1024MB, and the ephemeral storage to 2048MB | ||
#Please be sure to look carefully through this script as there may be locations you should update/edit yout Artifactory paths or AWS Secret IDs | ||
|
||
|
||
##### REPLACE THE FOLLOWING VALUES##### | ||
#Global Vars | ||
af_base = "https://artifactoryserver.somecompany.com/artifactory/" #Replace with your base URL for your Artifactory Server | ||
slackhook = "https://hooks.slack.com/services/ABCDEFG/123456789012345678901234567890" #Replace with the webhook URL To recieve Slack Notifications | ||
pols = { #Replace with the policy IDs for your environments | ||
"windows_dev":"123456789012345678901234567890", | ||
"mac_dev":"123456789012345678901234567890", | ||
"linux_dev":"123456789012345678901234567890", | ||
"windows_prd":"123456789012345678901234567890", | ||
"mac_prd":"123456789012345678901234567890", | ||
"linux_prd":"123456789012345678901234567890"} | ||
environ =["PRD","DEV"] | ||
afusername = "artifactory_user_name" #Replace with the username for your Artifactory Server | ||
EDR_API_URL = "https://AWS_API_GATEWAY.amazonaws.com" | ||
####################################### | ||
|
||
#!/usr/bin/env python3 | ||
from falconpy import APIHarness | ||
from falconpy import SensorDownload | ||
import requests | ||
import json | ||
import boto3 | ||
from multiprocessing import Process, Pipe | ||
|
||
############################################# | ||
## Script will pull the PRD/DEV versions ### | ||
## Update Versions list below if new exist ## | ||
############################################# | ||
# Versions List # | ||
rhel_ver = ["6","7","8","9"] | ||
rhel_arm_ver = ["8","9"] | ||
amz_ver = ["1","2","2023"] | ||
amz_arm_ver = ["2","2023"] | ||
sles_ver = ["11","12","15"] | ||
#Debian versions have to be uploaded in a weird way to support repos, please follow the existing format to add a new repo | ||
deb_ver = ["stretch","buster","bullseye","xenial","bionic","focal","jammy"] | ||
deb_arm_ver = ["bionic","focal","jammy"] | ||
|
||
|
||
# Function to grab secrets | ||
def get_secret(secid): | ||
session = boto3.session.Session() | ||
client = session.client(service_name='secretsmanager', region_name="us-east-1") | ||
try: | ||
get_secret_value_response = client.get_secret_value(SecretId=secid) | ||
except ClientError as e: | ||
raise e | ||
|
||
# Decrypts secret using the associated KMS key. | ||
secrets = json.loads(get_secret_value_response['SecretString']) | ||
if secid == "Sensor_Download_Key": | ||
sec1 = secrets['clientid'] | ||
sec2 = secrets['clientsec'] | ||
else: | ||
sec1 = secrets['afkey'] | ||
sec2 = "dummykey" | ||
return sec1, sec2 | ||
|
||
def alert_slack(message): | ||
requests.post(slackhook, headers = {"Content-Type": "application/json"}, json = {"text": message}) | ||
print("Slack Message Sent:",message) | ||
|
||
def do_the_needful(filters,uri,env): | ||
response = falcon.command("GetCombinedSensorInstallersByQuery", filter=filters) | ||
try: | ||
name = response["body"]["resources"][0]["name"] | ||
sha = response["body"]["resources"][0]["sha256"] | ||
os = response["body"]["resources"][0]["os"] | ||
os_ver = response["body"]["resources"][0]["os_version"] | ||
|
||
if os == "Ubuntu": | ||
deb_dist = "" | ||
if "amd64" in name: | ||
arch = "x86_64" | ||
for i in deb_ver: | ||
deb_dist = deb_dist + "deb.distribution=" + i + ";" | ||
elif "arm64" in name: | ||
arch = "arm64" | ||
for i in deb_arm_ver: | ||
deb_dist = deb_dist + "deb.distribution=" + i + ";" | ||
else: | ||
message = ":warning: Hey,\nUnknown Arch found in Sensor Version Download Script\n" + name | ||
alert_slack(message) | ||
|
||
fname="falcon-sensor-current-" + env + '.' + arch +".deb" | ||
afurl=af_base + uri + fname + ";" + deb_dist + "deb.component=main;deb.architecture=" + arch | ||
|
||
elif os == "macOS" or os == "Windows": | ||
fname="current-" + env + '.' + name | ||
afurl=af_base + uri + fname | ||
|
||
elif os.startswith("RHEL") or os == "Amazon Linux" or os == "SLES": | ||
if "x86_64" in name: | ||
arch = ".x86_64" | ||
elif "aarch64" in name: | ||
os_ver = os_ver[0] | ||
arch = ".aarch64" | ||
else: | ||
message = ":warning: Hey,\nUnknown Arch found in Sensor Version Download Script\n" + name | ||
alert_slack(message) | ||
|
||
#Setnaming convention for Amzn vs RHEL | ||
if os.startswith("RHEL"): | ||
conv = "el" | ||
elif os == "Amazon Linux": | ||
conv = "amzn" | ||
elif os == "SLES": | ||
conv = "SLES" | ||
|
||
fname="falcon-sensor-current-" + env + '.' + conv + os_ver + arch +".rpm" | ||
afurl=af_base + uri + os_ver + "/" + fname | ||
|
||
else: | ||
pass | ||
|
||
path="/tmp/" + fname | ||
try: | ||
#Download the installer | ||
download_response = falcond.download_sensor_installer(id=sha, | ||
download_path="/tmp/", file_name=fname) | ||
except: | ||
print("Error Downloading\n",os,env,fname) | ||
try: | ||
#Upload to Artifactory | ||
response1=requests.put(afurl,auth=(afusername, afkey), data=open(path,'rb').read(),verify=False) | ||
except: | ||
print("Error Uploading ",os,env,fname) | ||
print("Installer Downloaded:\n",os,env,filters,"\n",name,download_response['body']) | ||
print(fname," Artifactory upload:",response1) | ||
|
||
except: | ||
print("Error with ",filters) | ||
|
||
def macos(env, conn): | ||
#MacOS | ||
uri = "generic-edr-release/macos/" | ||
filters="platform:'mac'+version:'"+vers["mac_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("macos done") | ||
conn.close() | ||
|
||
def windows(env, conn): | ||
#Windows | ||
uri = "generic-edr-release/windows/" | ||
filters="platform:'windows'+version:'"+vers["windows_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("windows done") | ||
conn.close() | ||
|
||
#Linux | ||
def rhel(env, conn): | ||
#RHEL | ||
uri = "rpm-edr-release/el" | ||
for i in rhel_ver: | ||
filters="platform:'linux'+os:'RHEL*'+os_version:'" + i + "'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("rhel done") | ||
conn.close() | ||
|
||
def rhel_arm(env, conn): | ||
#RHEL ARM | ||
uri = "rpm-edr-release/el" | ||
for i in rhel_arm_ver: | ||
filters="platform:'linux'+os:'RHEL*'+os_version:'" + i + " - arm64'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("rhel_arm done") | ||
conn.close() | ||
|
||
|
||
def amz(env, conn): | ||
#AMZ | ||
uri = "rpm-edr-release/amzn" | ||
for i in amz_ver: | ||
filters="platform:'linux'+os:'Amazon Linux'+os_version:'" + i + "'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("amz done") | ||
conn.close() | ||
|
||
def amz_arm(env, conn): | ||
#AMZ ARM | ||
uri = "rpm-edr-release/amzn" | ||
for i in amz_arm_ver: | ||
filters="platform:'linux'+os:'Amazon Linux'+os_version:'" + i + " - arm64'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("amz_arm done") | ||
conn.close() | ||
|
||
#SLES | ||
def sles(env, conn): | ||
uri = "rpm-edr-release/SLES" | ||
for i in sles_ver: | ||
filters="platform:'linux'+os:'SLES'+os_version:'" + i + "'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("sles done") | ||
conn.close() | ||
|
||
def deb(env, conn): | ||
#Deb x86_64 | ||
uri = "debian-edr-release/" | ||
filters="platform:'linux'+os:'Ubuntu'+os_version:!'*arm64'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("deb done") | ||
conn.close() | ||
|
||
def deb_arm(env, conn): | ||
#Deb arm64 | ||
uri = "debian-edr-release/" | ||
filters="platform:'linux'+os:'Ubuntu'+os_version:'*arm64'+version:'"+vers["linux_"+env.lower()]+"'" | ||
do_the_needful(filters,uri,env) | ||
conn.send("deb_arm done") | ||
conn.close() | ||
|
||
def lambda_handler(event, context): | ||
print("Congrats! It's time to run this puppy and update the Sensor Installers!!!") | ||
global afkey, falcon, falcond, vers | ||
vers = {} | ||
print("Logging In") | ||
clientid, clientsec = get_secret("Sensor_Download_Key") | ||
afkey, afdummykey = get_secret("Artifactory_User_Key") | ||
falcon = APIHarness(client_id=clientid, client_secret=clientsec) | ||
falcond = SensorDownload(client_id=clientid, client_secret=clientsec) | ||
|
||
#Get Current Dev/Prd Version Numbers | ||
for name,pol in pols.items(): | ||
version = falcon.command("getSensorUpdatePoliciesV2", ids=pol)["body"]["resources"][0]["settings"]["sensor_version"] | ||
add = {name: version} | ||
vers.update(add) | ||
|
||
for env in environ: | ||
print("Starting Multiprocess Pool for") | ||
parent_conn, child_conn = Pipe() | ||
p1 = Process(target=macos, args=(env, child_conn)) | ||
p2 = Process(target=windows, args=(env, child_conn)) | ||
p3 = Process(target=rhel, args=(env, child_conn)) | ||
p4 = Process(target=rhel_arm, args=(env, child_conn)) | ||
p5 = Process(target=amz, args=(env, child_conn)) | ||
p6 = Process(target=amz_arm, args=(env, child_conn)) | ||
p7 = Process(target=sles, args=(env, child_conn)) | ||
p8 = Process(target=deb, args=(env, child_conn)) | ||
p9 = Process(target=deb_arm, args=(env, child_conn)) | ||
|
||
p1.start() | ||
p2.start() | ||
p3.start() | ||
p4.start() | ||
p5.start() | ||
p6.start() | ||
p7.start() | ||
p8.start() | ||
p9.start() | ||
|
||
for i in range(9): | ||
print(parent_conn.recv()) | ||
|
||
p1.join() | ||
p2.join() | ||
p3.join() | ||
p4.join() | ||
p5.join() | ||
p6.join() | ||
p7.join() | ||
p8.join() | ||
p9.join() | ||
|
||
message = "The Sensor Installer Updater script has completed. :lambda_lambda_lambda:" | ||
alert_slack(message) | ||
print("\n**********************") | ||
print("**Transfers Complete!*") | ||
print("**********************") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
The EDR API is setup as a single Lambda Function with an AWS API Gateway that passes the requests to this function to perform as a poor man's API. It is backended by a DynamoDB which stores the information regarding the latest kernels, sensor date changes, sensor versions, etc. Each .py file in this folder is a separate file within the lambda. | ||
|
||
There is also a health check endpoint that allows a team to call the API to check the health of an individual host programatically. In order to use the Health Check you will need to add the FalconPy Layer to this lambda and provid an SecretManager key with host read permissions. | ||
|
||
You will need to setup a DynamoDB with the following Tables to make full use of this Function, along with setting up the appropriate IAM policies to allow the poor man's API to operate properly: | ||
|
||
Container_Paths | ||
Latest_Kernel | ||
Sensor_Versions | ||
Proposed_Sensor Versions | ||
Change_Dates #(Dates will be manually added in the format of yyyy-mm-dd. EX: 2023-01-01) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import json | ||
import boto3 | ||
|
||
def update_container_paths_db(event): | ||
dynamo_resource = boto3.resource("dynamodb") | ||
container_table = dynamo_resource.Table("Container_Paths") | ||
body=event["body"] | ||
print(body) | ||
data = json.loads(body) | ||
for key, value in data.items(): | ||
path_update = dict( | ||
version = key, | ||
location = value) | ||
container_table.put_item(Item = path_update) | ||
|
||
message = [] | ||
message.append({"Update Status": "Complete"},) | ||
data_body = json.dumps(message) | ||
code = 200 | ||
return code,data_body | ||
|
||
def container_paths_db(version): | ||
dynamo_resource = boto3.resource("dynamodb") | ||
container_table = dynamo_resource.Table("Container_Paths") | ||
print(version) | ||
message = [] | ||
if version == "all": | ||
response = container_table.scan() | ||
data = {} | ||
for i in response["Items"]: | ||
out = {i["version"]: i["location"]} | ||
data.update(out) | ||
message.append(data) | ||
else: | ||
response = container_table.get_item(Key={"version": version})["Item"]["location"] | ||
message.append({version: response},) | ||
print(response) | ||
|
||
data_body = json.dumps(message) | ||
code = 200 | ||
return code,data_body |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import json | ||
import boto3 | ||
from falconpy import APIHarness | ||
|
||
# Function to grab secrets | ||
def get_falcon_secret(): | ||
session = boto3.session.Session() | ||
client = session.client(service_name='secretsmanager', region_name="us-east-1") | ||
try: | ||
get_secret_value_response = client.get_secret_value(SecretId="HostSecretKey") | ||
except ClientError as e: | ||
raise e | ||
|
||
# Decrypts secret using the associated KMS key. | ||
secrets = json.loads(get_secret_value_response['SecretString']) | ||
clientid = secrets['clientid'] | ||
clientsec = secrets['clientsec'] | ||
return clientid, clientsec | ||
|
||
def health_check(event): | ||
clientid, clientsec = get_falcon_secret() | ||
falcon = APIHarness(client_id=clientid, client_secret=clientsec) | ||
print("Getting host details") | ||
body=event["body"] | ||
if body is not None: | ||
data = json.loads(body) | ||
for key, value in data.items(): | ||
if key.lower() == "aid": | ||
aid = value.lower().replace("-","") | ||
else: | ||
return 400, "Error: Invalid JSON body, missing 'aid' key" | ||
else: | ||
return 400, "Error: Invalid JSON body, missing 'aid' key" | ||
response = falcon.command("GetDeviceDetails", ids=aid) | ||
if response["status_code"] == 200: | ||
print("Formatting host details") | ||
from datetime import datetime, timedelta | ||
offset = (datetime.utcnow() - timedelta(minutes = 15)).strftime('%Y-%m-%dT%H:%M:%SZ') | ||
host_detail=response["body"]["resources"] | ||
print(host_detail) | ||
tag = [] | ||
for detail in host_detail: | ||
rfm = detail["reduced_functionality_mode"] | ||
last_seen = detail["last_seen"] | ||
tag = detail["tags"] | ||
groups = detail["groups"] | ||
tags = "" | ||
for i in tag: | ||
tags += i.replace('SensorGroupingTags/','') | ||
tags += ", " | ||
else: | ||
return response["status_code"], response["body"]["errors"][0]["message"] | ||
if last_seen > offset: | ||
online = True | ||
else: | ||
online = False | ||
if rfm == "no": | ||
rfm = False | ||
if rfm == "yes": | ||
rfm = True | ||
message = {"AID": aid, "RFM (Should be False)": rfm, "Online": online, "Tags": tags, "Groups": groups} | ||
print(message) | ||
data_body = json.dumps(message) | ||
code = 200 | ||
return code,data_body |
Oops, something went wrong.