Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: CrowdStrike/falconpy
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.4.0
Choose a base ref
...
head repository: CrowdStrike/falconpy
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Jan 31, 2024

  1. Copy the full SHA
    f201440 View commit details
  2. Copy the full SHA
    c57bd83 View commit details
  3. Copy the full SHA
    caf3480 View commit details
  4. Copy the full SHA
    b912fd0 View commit details
  5. Copy the full SHA
    ece1929 View commit details
  6. Copy the full SHA
    684fa2c View commit details
  7. Copy the full SHA
    cbae7e6 View commit details
  8. Update enum.

    jshcodes committed Jan 31, 2024
    Copy the full SHA
    d42dba8 View commit details
  9. Copy the full SHA
    2e470e0 View commit details
  10. Copy the full SHA
    dd74f9a View commit details
  11. Copy the full SHA
    c8ec512 View commit details
  12. Copy the full SHA
    2b3e87c View commit details
  13. Bump version -> 1.4.1

    jshcodes committed Jan 31, 2024
    Copy the full SHA
    17e9db8 View commit details
  14. Added two missing action into host action.

    Shubham Kumar authored and jshcodes committed Jan 31, 2024
    Copy the full SHA
    001c09c View commit details
  15. Update hosts.py

    Remove whitespace
    jshcodes committed Jan 31, 2024
    Copy the full SHA
    1cef597 View commit details
  16. Copy the full SHA
    5d08836 View commit details
  17. Update wordlist.txt

    jshcodes committed Jan 31, 2024
    Copy the full SHA
    9b7a437 View commit details
  18. Update CHANGELOG.md

    jshcodes committed Jan 31, 2024
    Copy the full SHA
    cf11bf1 View commit details
  19. Skip unsupported SSL unit test on usgov1

    jshcodes committed Jan 31, 2024
    Copy the full SHA
    44a9ac5 View commit details
  20. Bump github/codeql-action from 2 to 3

    Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
    - [Release notes](https://github.com/github/codeql-action/releases)
    - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
    - [Commits](github/codeql-action@v2...v3)
    
    ---
    updated-dependencies:
    - dependency-name: github/codeql-action
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and jshcodes committed Jan 31, 2024
    Copy the full SHA
    985b68c View commit details
  21. Bump actions/setup-python from 4 to 5

    Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
    - [Release notes](https://github.com/actions/setup-python/releases)
    - [Commits](actions/setup-python@v4...v5)
    
    ---
    updated-dependencies:
    - dependency-name: actions/setup-python
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and jshcodes committed Jan 31, 2024
    Copy the full SHA
    da0f7c1 View commit details

Commits on Feb 13, 2024

  1. Copy the full SHA
    df10190 View commit details
  2. Update wordlist.txt

    jshcodes committed Feb 13, 2024
    Copy the full SHA
    10917e9 View commit details
  3. Remove example_auth.py

    jshcodes committed Feb 13, 2024
    Copy the full SHA
    03b004d View commit details
  4. Copy the full SHA
    42a89c1 View commit details
  5. Copy the full SHA
    a4a8f83 View commit details
  6. Copy the full SHA
    64c9bc3 View commit details
  7. Rename folders

    jshcodes committed Feb 13, 2024
    Copy the full SHA
    5850971 View commit details
  8. Copy the full SHA
    fb76abb View commit details
  9. Copy the full SHA
    dd23afe View commit details
  10. Copy the full SHA
    2e255d3 View commit details
  11. Copy the full SHA
    d7a06ed View commit details
  12. Copy the full SHA
    10e0428 View commit details
  13. Copy the full SHA
    7249a30 View commit details
  14. Copy the full SHA
    9f678e9 View commit details
  15. Copy the full SHA
    207948c View commit details
  16. Copy the full SHA
    2414244 View commit details
  17. Copy the full SHA
    e776dde View commit details
  18. Copy the full SHA
    291498e View commit details
  19. Copy the full SHA
    d24fdab View commit details
  20. Copy the full SHA
    b85a14a View commit details
  21. Copy the full SHA
    558ca2e View commit details
  22. Copy the full SHA
    a8ab97e View commit details
  23. Copy the full SHA
    7fdf36e View commit details
  24. Copy the full SHA
    de24e1f View commit details
  25. Update Samples home

    jshcodes committed Feb 13, 2024
    Copy the full SHA
    2a9dda2 View commit details
  26. Update AUTHORS.md

    jshcodes committed Feb 13, 2024
    Copy the full SHA
    5d60621 View commit details
  27. Copy the full SHA
    9dd5330 View commit details
  28. Update Malqueryinator (adds debugging, environment authentication)

    jshcodes committed Feb 13, 2024
    Copy the full SHA
    1885705 View commit details

Commits on Mar 20, 2024

  1. fix: requirements-dev.txt to reduce vulnerabilities

    The following vulnerabilities are fixed by pinning transitive dependencies:
    - https://snyk.io/vuln/SNYK-PYTHON-BANDIT-6241859
    snyk-bot authored and jshcodes committed Mar 20, 2024
    Copy the full SHA
    21bdff0 View commit details
Showing 343 changed files with 30,853 additions and 2,579 deletions.
189 changes: 189 additions & 0 deletions .github/wordlist.txt
Original file line number Diff line number Diff line change
@@ -1275,3 +1275,192 @@ ReadContainersByDateRangeCount
ReadContainerCountByRegistry
FindContainersCountAffectedByZeroDayVulnerabilities
ReadVulnerableContainerImageCount
QueryActivityByCaseID
CAwsAccount
GetD
Enums
shubham
WorkflowUpdateHumanInputV
WorkflowGetHumanInputV
WorkflowDefinitionsCreate
WorkflowDefinitionsUpdate
WorkflowDefinitionsImport
WorkflowDefinitionsExport
WorkflowExecutionsCombined
WorkflowDefinitionsCombined
DeletePolicyGroup
UpdatePolicyGroups
CreatePolicyGroups
ReadPolicyGroups
UpdatePolicyExclusions
ReadPolicyExclusions
DeletePolicy
ReadPolicies
CGCPUserScriptsAttachment
CGCPServiceAccountsExt
CGCPAccount
ConnectD
DeleteD
GetDiscoverCloudAzureUserScriptsAttachment
GetCSPMGCPServiceAccountsExt
ConnectCSPMGCPAccount
UpdateCSPMGCPAccount
DeleteCSPMGCPAccount
CreateCSPMAzureManagementGroup
GetCSPMAzureManagementGroup
ReadContainerAlertsCountBySeverity
PostAggregatesAlertsV
EDR
programmatically
MLE
SVE
RFM
hostnames
APIHarnessV
TCPdump
TCP
getchildren
rfm
untag
getioaexclusionsv
queryioaexclusionsv
getmlexclusionsv
mle
querymlexclusionsv
createpreventionpolicies
getscripts
listfiles
getsensorvisibilityexclusionsv
querysensorvisibilityexclusionsv
sve
DeleteFileV
ListFilesV
batchgetcmd
batchgetcmdstatus
deletefilev
listfilesv
ArchiveUploadV
WorkflowMockExecute
WorkflowExecuteInternal
GetSensorInstallersByQueryV
GetSensorInstallersEntitiesV
DownloadSensorInstallerByIdV
GetCombinedSensorInstallersByQueryV
QueryMitreAttacksForMalware
QueryMalware
GetMalwareEntities
IngestDataAsyncV
AggregateSupportIssues
hostsV
iot
ValidateCSPMGCPServiceAccountExt
GetCSPMGCPValidateAccountsExt
DeleteCSPMAzureManagementGroup
GetRuntimeDetectionsCombinedV
GetScanReport
CreateDeploymentEntity
ReadDeploymentsEntities
ReadDeploymentsCombined
instantiation
UpdateCSPMGCPServiceAccountsExt
UpdateD
RoemIko
GetCombinedPluginConfigs
CAWSAccountScriptsAttachment
getActionsMixin
startActions
getContents
signalChangesExternal
queryActionsMixin
RequestDeviceEnrollmentV
ThreatGraph
WorkflowActivitiesCombined
WorkflowTriggersCombined
Destom
ValueError
QueryCasesIdsByFilter
SDKDEMO
kube
KPA
argparse
colorama
Oke
Okumo
Moomaw
Esha
Kumar
gansel
Ansel
zipp
aaf
ExecuteCommandProxy
ASPM
UpsertBusinessApplications
GetExecutorNodes
UpdateExecutorNode
CreateExecutorNode
DeleteExecutorNode
GetIntegrationTasks
CreateIntegrationTask
UpdateIntegrationTask
DeleteIntegrationTask
RunIntegrationTask
GetIntegrationTypes
GetIntegrations
CreateIntegration
UpdateIntegration
DeleteIntegration
ExecuteQuery
ServiceNowGetDeployments
ServiceNowGetServices
GetServicesCount
GetServiceViolationTypes
GetTags
UpsertTags
DeleteTags
GetCredentialsIAC
CombinedBaseImages
CreateBaseImageEntities
DeleteBaseImages
ListObjectsByVersion
SearchObjectsByVersion
GetVersionedObject
PutObjectByVersion
DeleteVersionedObject
GetVersionedObjectMetadata
DataScanner
DeliverySettings
GetDeliverySettings
PostDeliverySettings
DownloadFile
EnumerateFile
IngestData
resultset
ReadNamespacesByDateRangeCount
ReadNamespaceCount
Mixin
UploadFileMixin
GetScanResult
LaunchScan
DeleteScanResult
QueryScanResults
GetSensorUsageWeekly
gethostgroups
queryhostgroups
WorkflowExecutionsResults
gui
workflowdefinitionscombined
workflowdefinitionsexport
workflowdefinitionsimport
workflowexecute
workflowexecutionscombined
workflowexecutionsresults
YYYY
dropdown
YAML
Prefilling
Autostarting
logfile
termcolor
darkdetect
Jesko
2 changes: 1 addition & 1 deletion .github/workflows/bandit.yml
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
2 changes: 1 addition & 1 deletion .github/workflows/dev-deploy.yml
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ jobs:

- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.x'

2 changes: 1 addition & 1 deletion .github/workflows/flake8.yml
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
2 changes: 1 addition & 1 deletion .github/workflows/pydocstyle.yml
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
16 changes: 7 additions & 9 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
# This workflow will upload a Python Package using Hatch when a release is created

name: Publish Python Package

@@ -15,17 +14,16 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
pipx install hatch
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PACKAGE_API_ID }}
TWINE_PASSWORD: ${{ secrets.PACKAGE_API_SECRET }}
HATCH_INDEX_USER: ${{ secrets.PACKAGE_API_ID }}
HATCH_INDEX_AUTH: ${{ secrets.PACKAGE_API_SECRET }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
hatch build
hatch publish
4 changes: 2 additions & 2 deletions .github/workflows/unit_testing_eu1.yml
Original file line number Diff line number Diff line change
@@ -22,9 +22,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
2 changes: 1 addition & 1 deletion .github/workflows/unit_testing_macos.yml
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
4 changes: 2 additions & 2 deletions .github/workflows/unit_testing_ubuntu.yml
Original file line number Diff line number Diff line change
@@ -20,13 +20,13 @@ jobs:
strategy:
matrix:
# os: [macos-latest, windows-latest, ubuntu-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13-dev']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14-dev']
# runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
4 changes: 2 additions & 2 deletions .github/workflows/unit_testing_us2.yml
Original file line number Diff line number Diff line change
@@ -19,9 +19,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
4 changes: 2 additions & 2 deletions .github/workflows/unit_testing_usgov1.yml
Original file line number Diff line number Diff line change
@@ -19,9 +19,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.7'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
2 changes: 1 addition & 1 deletion .github/workflows/unit_testing_windows.yml
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
6 changes: 4 additions & 2 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -347,6 +347,8 @@ single-line-class-stmt=no
# else.
single-line-if-stmt=no

# Number of positional arguments to a method
max-positional-arguments=20

[IMPORTS]

@@ -387,8 +389,8 @@ preferred-modules=
[EXCEPTIONS]

# Exceptions that will emit a warning when caught.
overgeneral-exceptions=BaseException,
Exception
overgeneral-exceptions=builtins.BaseException,
builtins.Exception


[REFACTORING]
21 changes: 18 additions & 3 deletions AUTHORS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

@@ -28,7 +29,6 @@ These coders deserve accolades and laurels as well as cool titles and cartoons.

| Name | Role |
| :--- | :--- |
| Šimon L., `@isimluk` | [Sanity Checker](https://xkcd.com/1926/) |
| Josh Lang, `@jlangdev` | [Lint Purveyor](https://xkcd.com/1833/) |
| Christopher Hammond, `@ChristopherHammond13` | [Technical Debt Collector](https://xkcd.com/2138/) |
| Gabe Alford, `@redhatrises` | [Git Whisperer](https://xkcd.com/1597/) |
@@ -37,6 +37,9 @@ These coders deserve accolades and laurels as well as cool titles and cartoons.
| Shane Shellenbarger, `@soggysec` | [Calamity Validator](https://xkcd.com/1700/) |
| Steve Klassen, `@mrxinu` | [Dilemma Responder](https://xkcd.com/85/) |

#### Honorable mentions
+ Šimon L., `@isimluk` ([Sanity Checker](https://xkcd.com/1926/))

## Contributors
The following members of the community have made requests, suggestions, code contributions or provided feedback and reported bugs.
This has been a critical element in the development of the FalconPy project.
@@ -90,6 +93,18 @@ This has been a critical element in the development of the FalconPy project.
+ Phil Massyn, `@massyn`
+ Russell Snyder, `@rusnyder`
+ `@PeroSoy`
+ Shubham, `@i-shubham01`
+ Don "Swanson" I., `@Don-Swanson-Adobe`
+ Nick, `nickforsythbarr`
+ `nesies`
+ `David-M-Berry`
+ Oke Okumo, `@okewoma`
+ Alexander Moomaw, `@alhumaw`
+ Esha Kumar, `@exk200006`
+ Griffin Ansel, `@gansel51`
+ `@am-cs-se`
+ Jesko, `@jhhcs`
+ Evan Stoner, `@evanstoner`


## Sponsors
@@ -98,11 +113,11 @@ Without the support of these executives, the FalconPy project would not have hap
| Name | Role |
| :-- | :-- |
| Chris Kachigian, `@ckachigian` | Herder of Cats |
| Rekha Das | Gatekeeper |
| Robbie Coleman, `@erraggy` | Keymaster |
| Mike Cryer | Colonel-in-Chief |

#### Honorable mentions
+ Rekha Das
+ Jaime Franklin, `@franklinjff`
+ Shawn Wells, `@shawndwells`

827 changes: 827 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)<br/>

@@ -9,7 +10,7 @@
[![Release date](https://img.shields.io/github/release-date/CrowdStrike/falconpy)](https://github.com/CrowdStrike/falconpy/releases)
[![Repo status](https://img.shields.io/osslifecycle/crowdstrike/falconpy?label=repo%20status)](https://github.com/CrowdStrike/falconpy/graphs/code-frequency)
[![Commit activity](https://img.shields.io/github/commits-since/CrowdStrike/falconpy/latest)](https://github.com/CrowdStrike/falconpy/commits/main)
![GitHub forks](https://img.shields.io/github/forks/crowdstrike/falconpy)
![GitHub forks](https://img.shields.io/github/forks/crowdstrike/falconpy?style=flat)

The FalconPy SDK contains a collection of Python classes that abstract CrowdStrike Falcon OAuth2 API interaction, removing duplicative code and allowing developers to focus on just the logic of their solution requirements.

@@ -31,7 +32,7 @@ The CrowdStrike Falcon SDK for Python completely abstracts token management, whi
[![Development Installs](https://static.pepy.tech/personalized-badge/crowdstrike-falconpy-dev?left_text=development%20package%20installs/month&left_color=grey&right_color=blue&period=month)](https://pepy.tech/project/crowdstrike-falconpy-dev)

#### Supported versions of Python
The CrowdStrike Falcon SDK for Python was developed for Python 3. Current versions of FalconPy provide support for Python versions `3.7` - `3.12`. Every commit to the FalconPy code base is unit tested for functionality using all versions of Python the library currently supports.
The CrowdStrike Falcon SDK for Python was developed for Python 3. Current versions of FalconPy provide support for Python versions `3.7` - `3.13`. Every commit to the FalconPy code base is unit tested for functionality using all versions of Python the library currently supports.

> [!NOTE]
> Developers working with Python version `3.6` will need to leverage versions of FalconPy less than `1.4.0`.
@@ -83,6 +84,7 @@ For each CrowdStrike Falcon API service collection, a matching Service Class is

- Closely follows Python and OpenAPI best practice for code style and syntax. PEP-8 compliant.
- Completely abstracts token management, automatically refreshing your token when it expires.
- Interact with newly released API operations not yet available in the library via the `override` method.
- Provides simple programmatic patterns for interacting with CrowdStrike Falcon APIs.
- Supports [cloud region autodiscovery](https://www.falconpy.io/Usage/Environment-Configuration.html#cloud-region-autodiscovery) for the CrowdStrike `US-1`, `US-2` and `EU-1` regions.
- Supports dynamic [configuration](https://www.falconpy.io/Usage/Environment-Configuration.html) based upon the needs of your environment.
@@ -134,13 +136,17 @@ from falconpy import Hosts
# CrowdStrike does not recommend you hardcode credentials within source code.
# Instead, provide these values as variables that are retrieved from the environment,
# read from an encrypted file or secrets store, provided at runtime, etc.
# This example retrieves credentials from the environment as the variables
# "FALCON_CLIENT_ID" and "FALCON_CLIENT_SECRET".

hosts = Hosts(client_id=os.getenv("FALCON_CLIENT_ID"),
client_secret=os.getenv("FALCON_CLIENT_SECRET")
)

# While this example retrieves credentials from the environment as the variables
# "FALCON_CLIENT_ID" and "FALCON_CLIENT_SECRET". Developers leveraging environment
# authentication do not need to specify the client_id or client_secret keywords.
#
# hosts = Hosts()

SEARCH_FILTER = "hostname-search-string"

# Retrieve a list of hosts that have a hostname that matches our search filter
3 changes: 2 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

3 changes: 2 additions & 1 deletion SUPPORT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

164 changes: 0 additions & 164 deletions dev_setup.py

This file was deleted.

3 changes: 2 additions & 1 deletion docs/PACKAGING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

Binary file modified docs/asset/cs-logo-footer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/asset/cs-logo-red.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/asset/cs-logo-small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/asset/cs-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/asset/redfalcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "crowdstrike-falconpy"
dynamic = ["version"]
description = "The CrowdStrike Falcon SDK for Python"
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.7"
authors = [
{ name = "CrowdStrike", email = "falconpy@crowdstrike.com" },
]
maintainers = [
{ name = "Joshua Hiller", email = "falconpy@crowdstrike.com" },
]
keywords = [
"api",
"crowdstrike",
"crowdstrike-falcon",
"devsecops",
"falcon",
"oauth2",
"sdk"
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Flake8",
"Intended Audience :: Developers",
"License :: OSI Approved :: The Unlicense (Unlicense)",
"Natural Language :: English",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Operating System :: POSIX :: Linux",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Security",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Systems Administration",
"Topic :: Utilities",
]
dependencies = [
"requests",
"urllib3",
]

[project.optional-dependencies]
dev = [
"bandit",
"coverage",
"flake8",
"pydocstyle",
"pylint",
"pytest",
"pytest-cov"
]

[project.urls]
Documentation = "https://www.falconpy.io"
Homepage = "https://github.com/CrowdStrike/falconpy"
Source = "https://github.com/CrowdStrike/falconpy/tree/main/src/falconpy"
Tracker = "https://github.com/CrowdStrike/falconpy/issues"

[tool.hatch.version]
source = "code"
path = "src/falconpy/_version.py"
expression = "_VERSION"

[tool.hatch.build.targets.sdist]
exclude = [
"/.github",
"/build",
"/cut",
"/docs",
"/htmlcov",
"/samples",
"/util",
"/.gitignore",
"/requirements.txt",
"/requirements-dev.txt"
]

[tool.hatch.build.targets.wheel]
package = [
"src/falconpy"
]
include = ["src/falconpy"]
sources = ["src"]
3 changes: 1 addition & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -10,11 +10,10 @@
# These packages are only required if you are a contributor,
# or wish to use FalconDebug.
#
bandit>=1.7.0 # via -r requirements-dev.in
bandit>=1.7.7 # via -r requirements-dev.in
coverage>=5.5 # via -r requirements-dev.in
flake8>=3.9.0 # via -r requirements-dev.in
pytest-cov>=2.11.1 # via -r requirements-dev.in
pytest>=6.2.2 # via -r requirements-dev.in
ipython>=8.10.0 # via -r requirements-dev.in, pinned by Snyk to avoid SNYK-PYTHON-IPYTHON-3318382
pydocstyle>=6.1.0
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid SNYK-PYTHON-SETUPTOOLS-3113904
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -4,6 +4,5 @@
#
# pip-compile requirements.in
#
requests>=2.27.1 # via -r requirements.in
urllib3>=1.26.10 # via -r requirements.in, requests

requests # via -r requirements.in
urllib3 # via -r requirements.in, requests
795 changes: 724 additions & 71 deletions samples/README.md

Large diffs are not rendered by default.

64 changes: 62 additions & 2 deletions samples/authentication/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

@@ -7,7 +8,8 @@ The examples in this folder focus on authentication to CrowdStrike's APIs.

- [Azure Key Vault Authentication](#azure-key-vault-authentication) - CrowdStrike API authentication leveraging Azure Key Vault for credential storage.
- [AES Authentication](#aes-authentication) - Leverage AES/CBC to encrypt credentials for use with authentication to the CrowdStrike API.
- [AES File Crypt](#aes-file-crypt) - Encrypt arbitrary files with AES/CBC.
- [AES File Crypt](#aes-file-crypt) - Encrypt arbitrary files with AES/CBC
- [AWS Parameter Store](#aws-parameter-store) - CrowdStrike API authentication leveraging AWS Parameter Store for credential storage
- [Token Authentication](#token-authentication) - Token Authentication is the original solution for authenticating to a Service Class, and is still fully supported. This example demonstrates how to use Token Authentication to interact with multiple Service Classes.

## Azure Key Vault Authentication
@@ -458,6 +460,64 @@ file arguments:
Source code for this example can be found [here](aes_file_crypt.py).

---
## AWS Parameter store
This application demonstrates storing CrowdStrike API credentials within the AWS Parameter Store service, and retrieving them to access the CrowdStrike API.

### Running the program
In order to run this demonstration, you will need access to CrowdStrike API keys. You will also need to set your specific AWS location

#### Command line arguments
This program accepts the following command line arguments.

| Argument | Long Argument | Description |
| :-- | :-- | :-- |
| `-h` | `--help` | Display command line help and exit |
| `-k` _CLIENT_ID_PARAMETER_ | `--client_id_parameter` _CLIENT_ID_PARAMETER_ | Name of the Key Vault Secrets parameter storing your API client ID |
| `-s` _CLIENT_SECRET_PARAMETER_ | `--client_secret_parameter` _CLIENT_SECRET_PARAMETER_ | Name of the Key Vault Secrets parameter storing your API client secret |
| `-d` | `--debug`| Enables debugging functionality |

#### Basic usage

##### Use this command to test out the sample.

```shell
python3 aws_parameter_store.py -k FALCON_CLIENT_ID -s FALCON_CLIENT_SECRET
```
##### Use this command to activate debugging.

```shell
python3 aws_parameter_store.py -k FALCON_CLIENT_ID -s FALCON_CLIENT_SECRET -d
```
#### Command-line help
Command-line help is available via the `-h` argument.

```shell
usage: aws_parameter_store.py [-h] [-k] CLIENT_ID [-s] CLIENT_SECRET [-d] DEGUG
___ ____ __ ____ _______.
/ \ \ \ / \ / / / |
/ ^ \ \ \/ \/ / | (----`
/ /_\ \ \ / \ \
/ _____ \ \ /\ / .----) |
/__/ \__\ \__/ \__/ |_______/
____ __ _____ __
/ __ \____ __________ _____ ___ ___ / /____ _____ / ___// /_____ ________
/ /_/ / __ `/ ___/ __ `/ __ `__ \/ _ \/ __/ _ \/ ___/ \__ \/ __/ __ \/ ___/ _ \
/ ____/ /_/ / / / /_/ / / / / / / __/ /_/ __/ / ___/ / /_/ /_/ / / / __/
/_/ \__,_/_/ \__,_/_/ /_/ /_/\___/\__/\___/_/ /____/\__/\____/_/ \___/
optional arguments:
-h, --help show this help message and exit
-d, --debug enables degugging
required arguments:
-k CLIENT_ID, --client_id_parameter CLIENT_ID
-s CLIENT_SECRET, --client_secret_parameter CLIENT_SECRET
```
## Token Authentication
[Token authentication](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#legacy-authentication) (also referred to as _legacy authentication_) is the process of authenticating to a FalconPy Service Class by providing a previously assigned bearer token directly to the [`auth_token`](https://www.falconpy.io/Usage/Basic-Service-Class-usage.html#legacy-authentication) keyword when instantiating the Service Class. This is the original method of authentication provided by Service Classes, and while it is frequently eschewed in preference to [Direct](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#direct-authentication) and [Object](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#object-authentication) [Authentication](https://www.falconpy.io/Usage/Authenticating-to-the-API.html), there are multiple scenarios where it is still the best option for the situation.
25 changes: 20 additions & 5 deletions samples/authentication/aws_parameter_store.py
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
This application demonstrates storing CrowdStrike API credentials within the
AWS Parameter Store service, and retrieving them to access the CrowdStrike API.
"""
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
try:
import boto3
@@ -64,8 +65,19 @@ def consume_arguments() -> Namespace:
default="FALCON_CLIENT_SECRET",
dest="client_secret_parameter"
)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)

parsed = parser.parse_args()
if parsed.debug:
logging.basicConfig(level=logging.DEBUG)


return parsed

return parser.parse_args()


def get_parameter_store_params(cmd_line: Namespace):
@@ -101,9 +113,9 @@ def get_parameter_store_params(cmd_line: Namespace):
return returned_client_id, returned_client_secret


def perform_simple_demonstration(client_id: str, client_secret: str):
def perform_simple_demonstration(client_id: str, client_secret: str, debug: bool):
"""Perform a simple API demonstration using the credentials retrieved."""
falcon = Hosts(client_id=client_id, client_secret=client_secret)
falcon = Hosts(client_id=client_id, client_secret=client_secret, debug=debug)
# Retrieve 500 hosts and sort ascending by hostname
aid_lookup = falcon.query_devices_by_filter_scroll(sort="hostname.asc", limit=500)
if not aid_lookup["status_code"] == 200:
@@ -120,6 +132,9 @@ def perform_simple_demonstration(client_id: str, client_secret: str):


if __name__ == "__main__":
# Consume our command line, retrieve our credentials from AWS parameter store
# Consume our command line arguments
args = consume_arguments()
# retrieve our credentials from AWS parameter store
client_id, client_secret = get_parameter_store_params(args)
# and then execute a simple API demonstration to prove functionality.
perform_simple_demonstration(*get_parameter_store_params(consume_arguments()))
perform_simple_demonstration(client_id, client_secret)
2 changes: 2 additions & 0 deletions samples/authentication/requirements_aws_parameter_store.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
boto3
crowdstrike-falconpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
boto3
click
colorama
crowdstrike-falconpy
31 changes: 28 additions & 3 deletions samples/authentication/token_authentication_example.py
Original file line number Diff line number Diff line change
@@ -34,9 +34,11 @@
This sample should run using any version of FalconPy and requires the colorama and click libraries.
"""
import logging
import os
import click
import colorama
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import (
CloudConnectAWS,
Detects,
@@ -54,9 +56,27 @@
BOLD = colorama.Style.BRIGHT
ENDMARK = colorama.Style.RESET_ALL


def consume_arguments() -> Namespace:
parser = ArgumentParser(description=__doc__, fromatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-b", "--base-url",
dest="base_url",
help="CrowdStrike cloud region. (auto or usgov1, Default: auto)",
required=False,
default="usgov1"
)
parsed = parser.parse_args()
if parsed.debug:
logging.basicConfig(level=logging.DEBUG)


return parsed
# ### BEGIN token simulation
def get_token():
def get_token(debug=False):
"""
Generate a token to use for authentication.
@@ -95,7 +115,8 @@ def get_token():
)
auth = OAuth2(
client_id=falcon_client_id,
client_secret=falcon_client_secret
client_secret=falcon_client_secret,
debug=debug
)
# Generate a token
auth.token()
@@ -176,6 +197,10 @@ def passed(svc_class: str):


if __name__ == "__main__":
# Parse command-line arguments and retrieve debug mode setting
args = consume_arguments()
# Authenticate using Falcon API OAuth2 with debug mode enabled if specified
get_token(debug=args.debug)
# Test each of these classes to confirm cross collection authentication for Service Classes
classes_to_test = [CloudConnectAWS, Detects, Hosts, IOC, Incidents, Intel]
# Grab a simulated token and execute the test series
106 changes: 106 additions & 0 deletions samples/containers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Container examples
The examples in this folder focus on leveraging CrowdStrike's Container APIs to discover and manage your container assets.
- [kube_map - Discover your Kubernetes Attack Surface](#Discover-your-Kubernetes-Attack-Surface)

## Discover your Kubernetes Attack Surface
Discovers Kubernetes assets that are monitored by the Falcon Sensor (clusters, nodes, pods, and containers).

> [!IMPORTANT]
> Installing the __Kubernetes Protection Agent (KPA)__ on your clusters will result in the most accurate information.

### Running the program
In order to run this demonstration, you will need access to CrowdStrike API keys with the following scopes:
| Service Collection | Scope |
| :---- | :---- |
| Kubernetes Protection | __READ__|

### Execution syntax
This example accepts the following input parameters.
| Parameter | Purpose |
| :--- | :--- |
| `-d`, `--debug` | Enable API debugging. |
| `-c`, `--cluster` | Display all clusters and the number of attached nodes. |
| `-n`, `--node` | Display all nodes including the number of attached, active pods. |
| `-nn`, `--node_name` | Displays pods connected to a specific node. |
| `-t`, `--thread` | Enables asynchronous API calls for faster returns. |
| `-k`, `--key` | Your CrowdStrike Falcon API Client ID |
| `-s`, `--secret` | Your CrowdStrike Falcon API Client Secret |

Displays the number of clusters, nodes, pods, and containers detected by the Falcon Sensor.
```shell
python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET
```

Displays a table of cluster information.
```shell
python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -c
```

Displays a table of node information.
```shell
python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -n
```

Displays a table of pods based on it's parent node name using the optional threading feature.
```shell
python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -nn "node_name" -t
```

Displays API debug logging.
```shell
python3 kube_map.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d
```

#### Command-line help
Command-line help is available using the `-h` or `--help` parameters.

```shell
% python3 kube_map.py -h
usage: kube_map.py [-h] -k CLIENT_ID -s CLIENT_SECRET [-d] [-c] [-n] [-nn NODE_NAME] [-t]

_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
_ ___ _ ____ _____
| |/ / | | | __ )| ____|
| ' /| | | | _ \| _|
| . \| |_| | |_) | |___
__ __|_|\_\\___/|____/|_____|__ ____
| \/ | / \ | _ \| _ \| ____| _ \
| |\/| | / _ \ | |_) | |_) | _| | |_) |
| | | |/ ___ \| __/| __/| |___| _ <
|_| |_/_/ \_\_| |_| |_____|_| \_\
This sample utilizes the Kubernetes Protection service collection to map out
your kubernetes assets. Kubernetes assets are found via the Falcon Sensor.
Creation date: 06.26.23 - alhumaw
options:
-h, --help show this help message and exit
-d, --debug Enable API debugging
-c, --cluster Display clusters and it's nodes
-n, --node Display nodes and it's pods
-nn NODE_NAME, --node_name NODE_NAME
Display pods connected to a specific node
-t, --thread Enables asynchronous API calls for faster returns
required arguments:
-k CLIENT_ID, --client_id CLIENT_ID
CrowdStrike API client ID
-s CLIENT_SECRET, --client_secret CLIENT_SECRET
CrowdStrike API client secret
```
### Example source code
The source code for this example can be found [here](kube_map.py).
529 changes: 529 additions & 0 deletions samples/containers/kube_map.py

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions samples/cspm_registration/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon CSPM Registration samples
The examples within this folder focus on leveraging CrowdStrike's Falcon CSPM Registration API.
@@ -48,6 +51,10 @@ python3 get_cspm_policies.py -f $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -o fi
python3 get_cspm_policies.py -f $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -c aws
```

```shell
python3 get_cspm_policies.py -f $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d
```
> To activate debugging, use the `-d` argument.
#### Command-line help
Command-line help is available via the `-h` argument.

@@ -98,7 +105,8 @@ optional arguments:
Policy report output file (CSV format)
-c CLOUD, --cloud CLOUD
Cloud provider (aws, azure, gcp)
-d, --debug, Activates debugging
```
### Example source code
The source code for this example can be found [here](get_cspm_policies.py).
The source code for this example can be found [here](get_cspm_policies.py).
61 changes: 44 additions & 17 deletions samples/cspm_registration/get_cspm_policies.py
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@
import os
import sys
import logging
from argparse import ArgumentParser, RawTextHelpFormatter
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from tabulate import tabulate
try:
from falconpy import CSPMRegistration
@@ -49,17 +49,42 @@
) from no_falconpy


def consume_arguments() -> Namespace:
# Capture command line arguments
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-f", "--falcon_client_id",
help="Falcon Client ID", default=None, required=False)
parser.add_argument("-s", "--falcon_client_secret",
help="Falcon Client Secret", default=None, required=False)
parser.add_argument("-o", "--output_file",
help="Policy report output file (CSV format)", required=False)
parser.add_argument(
"-c", "--cloud", help="Cloud provider (aws, azure, gcp)", required=False)
args = parser.parse_args()
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-f", "--falcon_client_id",
help="Falcon Client ID",
default=None,
required=False)
parser.add_argument("-s", "--falcon_client_secret",
help="Falcon Client Secret",
default=None,
required=False)
parser.add_argument("-o", "--output_file",
help="Policy report output file (CSV format)",
required=False)
parser.add_argument("-c", "--cloud",
help="Cloud provider (aws, azure, gcp)",
required=False)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)

parsed = parser.parse_args()
return parsed


cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)



# pylint: disable=E0606

# Grab our client_id and client_secret or exit
CONFIG_FILE = '../config.json'
@@ -68,20 +93,22 @@
config = json.loads(file_config.read())
falcon_client_id = config['falcon_client_id']
falcon_client_secret = config['falcon_client_secret']
elif args.falcon_client_id is not None and args.falcon_client_secret is not None:
falcon_client_id = args.falcon_client_id
falcon_client_secret = args.falcon_client_secret
elif cmd_line.falcon_client_id is not None and cmd_line.falcon_client_secret is not None:
falcon_client_id = cmd_line.falcon_client_id
falcon_client_secret = cmd_line.falcon_client_secret
debug = cmd_line.debug if cmd_line.debug else False # Set debug mode based on argument
else:
logging.error(
" Please specify Falcon API Credentials with config.json or script arguments")
sys.exit()

data_file = args.output_file
cloud = args.cloud
data_file = cmd_line.output_file
cloud = cmd_line.cloud

# Instantiate CSPM_Registration service class
falcon = CSPMRegistration(client_id=falcon_client_id,
client_secret=falcon_client_secret
client_secret=falcon_client_secret,
debug=debug
)


2 changes: 2 additions & 0 deletions samples/cspm_registration/requirements_get_cspm_policies.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
crowdstrike-falconpy
tabulate
3 changes: 2 additions & 1 deletion samples/custom_ioa/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

3 changes: 2 additions & 1 deletion samples/detects/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

16 changes: 13 additions & 3 deletions samples/discover/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon Discover samples
The examples within this folder focus on leveraging CrowdStrike's Falcon Discover API.
@@ -55,6 +58,12 @@ python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -
python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -f simple
```

> Activate API debugging with the `-d` argument.
```shell
python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -d
```

##### Available table formats
Tabular results may be formatted using any of the format options listed below.

@@ -87,7 +96,7 @@ Command-line help is available via the `-h` argument.

```shell
python3 list_discovered_hosts.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -h
usage: list_discovered_hosts.py [-h] [-k CLIENT_ID] [-s CLIENT_SECRET] [-b BASE_URL] [-r] [-f FORMAT]
usage: list_discovered_hosts.py [-h] [-k CLIENT_ID] [-s CLIENT_SECRET] [-b BASE_URL] [-r] [-d] [-f FORMAT]

CrowdStrike Falcon Discover simple example.

@@ -115,7 +124,7 @@ CrowdStrike Falcon Discover simple example.
Creation date: 02.08.2022 - jshcodes@CrowdStrike
optional arguments:
options:
-h, --help show this help message and exit
-k CLIENT_ID, --client_id CLIENT_ID
CrowdStrike Falcon API key ID.
@@ -126,6 +135,7 @@ optional arguments:
-b BASE_URL, --base_url BASE_URL
CrowdStrike API region (us1, us2, eu1, usgov1) NOT required unless you are using `usgov1`.
-r, --reverse Reverse sort (defaults to ASC)
-d, --debug Enable API debugging
-f FORMAT, --format FORMAT
Table format to use for display.
(plain, simple, github, grid, fancy_grid, pipe, orgtbl, jira, presto,
68 changes: 37 additions & 31 deletions samples/discover/list_discovered_hosts.py
Original file line number Diff line number Diff line change
@@ -25,36 +25,36 @@
Creation date: 02.08.2022 - jshcodes@CrowdStrike
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter
from tabulate import tabulate
try:
from falconpy import Discover
from falconpy import Discover, Hosts
except ImportError as no_falconpy:
raise SystemExit("The crowdstrike-falconpy package must be installed "
"in order to run this progrem.\n\nInstall with the command: "
"in order to run this program.\n\nInstall with the command: "
"python3 -m pip install crowdstrike-falconpy") from no_falconpy


def parse_command_line() -> object:
"""Parse any received inbound command line parameters."""
parser = ArgumentParser(
description=__doc__,
formatter_class=RawTextHelpFormatter
)
)
parser.add_argument(
'-k',
'--client_id',
help='CrowdStrike Falcon API key ID.\n'
'You can also use the `FALCON_CLIENT_ID` environment variable to specify this value.',
required=False
)
)
parser.add_argument(
'-s',
'--client_secret',
help='CrowdStrike Falcon API key secret.\n'
'You can also use the `FALCON_CLIENT_SECRET` environment variable to specify this value.',
required=False
)
)
parser.add_argument(
'-b',
'--base_url',
@@ -68,7 +68,15 @@ def parse_command_line() -> object:
help='Reverse sort (defaults to ASC)',
required=False,
action="store_true"
)
)
parser.add_argument(
'-d',
'--debug',
help='Enable API debugging',
required=False,
default=False,
action="store_true"
)
parser.add_argument(
'-f',
'--format',
@@ -77,19 +85,13 @@ def parse_command_line() -> object:
'pretty, psql, rst, mediawiki, moinmoin, youtrack, html, unsafehtml, \n'
'latext, latex_raw, latex_booktabs, latex_longtable, textile, tsv)',
required=False
)

)
return parser.parse_args()


def get_sort_key(sorting) -> list:
"""Return the sort colum value for sorting operations."""
"""Return the sort column value for sorting operations."""
return sorting["hostname"]


# Retrieve all inbound command line parameters
# Retrieve all inbound command line parameters if args debug is present
args = parse_command_line()

# Set constants based upon received inputs
BASE_URL = "auto"
if args.base_url:
@@ -106,6 +108,10 @@ def get_sort_key(sorting) -> list:
SORT = False
else:
SORT = bool(args.reverse)
# add debug with logging put after parser
if args.debug:
logging.basicConfig(level=logging.DEBUG)

TABLE_FORMATS = [
"plain", "simple", "github", "grid", "fancy_grid", "pipe", "orgtbl", "jira", "presto",
"pretty", "psql", "rst", "mediawiki", "moinmoin", "youtrack", "html", "unsafehtml",
@@ -116,7 +122,6 @@ def get_sort_key(sorting) -> list:
table_format = args.format.strip().lower()
if table_format in TABLE_FORMATS:
TABLE_FORMAT = table_format

# Headers used in our result display table
HEADERS = {
"hostname": "Hostname",
@@ -125,33 +130,34 @@ def get_sort_key(sorting) -> list:
"plat": "Platform",
"osver": "Version"
}

hosts = Hosts(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
debug=args.debug
)
# Connect to the Discover API
discover = Discover(client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
base_url=BASE_URL
)

discover = Discover(auth_object=hosts)
# Empty list to hold our results
identified = []
# Query for a complete list of discovered hosts. Maxes out at 100.
host_lookup = discover.query_hosts()
if host_lookup["status_code"] == 200:
if host_lookup.get("status_code") == 200:
identified_hosts = host_lookup["body"]["resources"]
if not identified_hosts:
# No hosts returned for this search
print("No hosts identified")
else:
# Retrieve all details for all discovered hosts
host_detail = discover.get_hosts(ids=identified_hosts)["body"]["resources"]
# Add each hosts relevant detail to our `identified` list so we can display it
# Add each host's relevant detail to our `identified` list so we can display it
for host in host_detail:
found = {}
found["hostname"] = host.get("hostname", "Not identified")
found["current_local"] = host.get("current_local_ip", "Unknown")
found["current_external"] = host.get("external_ip", "Unknown")
found["plat"] = host.get("platform_name", "Unknown")
found["osver"] = host.get("os_version", "Unknown")
found = {
"hostname": host.get("hostname", "Not identified"),
"current_local": host.get("current_local_ip", "Unknown"),
"current_external": host.get("external_ip", "Unknown"),
"plat": host.get("platform_name", "Unknown"),
"osver": host.get("os_version", "Unknown")
}
# Append this result to our display list
identified.append(found)
# All findings have been tabulated, show the results
37 changes: 31 additions & 6 deletions samples/discover/spyglass.py
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
# pylint: disable=R0902,R0904,R0912,R0914,R0915
import os
import json
import logging
from datetime import datetime
from argparse import ArgumentParser, RawTextHelpFormatter
from concurrent.futures import ThreadPoolExecutor
@@ -66,6 +67,8 @@
class Application:
"""Class to store configuration and performance detail."""

_debug = False

_timing = {
"start_time": datetime.now().timestamp(),
"end_time": 0,
@@ -88,6 +91,7 @@ class Application:
_status = {"running": True, "cancelled": False}

def __init__(self):
"""Construct an instance of the application."""
self.configure_application()
if not self.show_updates:
print("Depending on the size of your environment, "
@@ -214,6 +218,11 @@ def configure_application(self):
default=None,
required=False
)
parser.add_argument("--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parsed = parser.parse_args()
cats = parsed.categories.split(",")
if "all" in cats:
@@ -243,11 +252,16 @@ def configure_application(self):
self.applications_filter = parsed.applications_filter
if parsed.applications_sort:
self.applications_sort = parsed.applications_sort
if parsed.debug:
self.debug = True
self.show_updates = False
logging.basicConfig(level=logging.DEBUG)

# Everything before this moment happens within milliseconds
self.sdk = Discover(client_id=parsed.falcon_client_id,
client_secret=parsed.falcon_client_secret,
base_url=parsed.region
base_url=parsed.region,
debug=self.debug
)
self.hosts = Hosts(auth_object=self.sdk)

@@ -288,6 +302,16 @@ def extra(self) -> dict:
return self._configuration["extra"]

# Mutable
@property
def debug(self) -> bool:
"""Retrieve the end time property."""
return self._debug

@debug.setter
def debug(self, val: bool):
"""Set the end time property."""
self._debug = val

@property
def end_time(self) -> int:
"""Retrieve the end time property."""
@@ -463,7 +487,7 @@ def batch_get_details(sdk, cat):
def get_detail(ids):
"""Retrieve detail information for the ID list provided."""
returned = False
if APP.running:
if APP.running: # pylint: disable=E0606
APP.api_calls += 1
returned = cmd(ids=ids)["body"]["resources"]
return returned
@@ -492,9 +516,9 @@ def get_detail(ids):
details.extend(get_detail(returned))
if APP.show_updates:
print(f" Details for {len(details)} {cat} retrieved.",
end=f"{' '*40}\r",
flush=True
)
end=f"{' '*40}\r",
flush=True
)
if running_total >= total or not APP.running:
running = False
else:
@@ -670,7 +694,7 @@ def display_accounts(account_list: list):
row_break()


def display_hosts(hosts_list: list):
def display_hosts(hosts_list: list): # noqa
"""Display the hosts results."""
print(category_header("Hosts"), end="\r")
row_break(23)
@@ -697,6 +721,7 @@ def display_hosts(hosts_list: list):
for host in hosts_list:
first_seen = ''
last_seen = ''
managed = ''
if host.get("first_seen_timestamp", None):
first_seen = datetime.strptime(host["first_seen_timestamp"], "%Y-%m-%dT%H:%M:%SZ")
first_seen = bold(first_seen.strftime("%m-%d-%Y %H:%M:%S"))
5 changes: 4 additions & 1 deletion samples/discover_aws/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon Discover for Cloud and Containers samples
The examples within this folder focus on leveraging CrowdStrike's Falcon Discover for Cloud and Containers API.
3 changes: 2 additions & 1 deletion samples/falconx_sandbox/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon Intelligence Sandbox examples
5 changes: 4 additions & 1 deletion samples/firewall_management/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon Firewall Management samples
The examples within this folder focus on leveraging CrowdStrike's Falcon Firewall Management API.
295 changes: 295 additions & 0 deletions samples/firewall_management/firedrill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
r"""FireDrill - Firewall Mangement service class walkthrough.
________ _____ _______ ________ ______ _______ _____ _____ _____
|_ __ ||_ _||_ __ \ |_ __ ||_ _ `.|_ __ \ |_ _||_ _| |_ _|
| |_ \_| | | | |__) | | |_ \_| | | `. \ | |__) | | | | | | |
| _| | | | __ / | _| _ | | | | | __ / | | | | _ | | _
_| |_ _| |_ _| | \ \_ _| |__/ | _| |_.' /_| | \ \_ _| |_ _| |__/ | _| |__/ |
|_____| |_____||____| |___||________||______.'|____| |___||_____||________||________|
Creates a new rule group, adds new rules, changes their order, modifies rule properties
deletes a rule, then deletes the rule group
02/27/24 - jlangdev@CrowdStrike
"""
import os
from random import SystemRandom
from argparse import ArgumentParser, RawTextHelpFormatter
try:
from falconpy import FirewallManagement
except (ImportError, ModuleNotFoundError) as no_falconpy:
raise SystemExit(
"The crowdstrike-falconpy package must be installed to run this program."
) from no_falconpy

RULES = [
{
"action": "ALLOW",
"address_family": "IP4",
"description": "test rule 1",
"direction": "IN",
"enabled": True,
"fields": [
{
"name": "image_name",
"value": "",
"type": "windows_path",
"values": []
}
],
"fqdn": "",
"fqdn_enabled": False,
"icmp": {"icmp_code": "", "icmp_type": ""},
"local_address": [{"address": "*", "netmask": 0}],
"local_port": [{"end": 1000, "start": 1}],
"log": False,
"monitor": {"count": "1", "period_ms": "1000000"},
"name": "rule1",
"protocol": "6",
"remote_address": [{"address": "10.0.77.101-104", "netmask": 0}],
"remote_port": [{"end": 1000, "start": 1}],
"temp_id": "1"
},
{
"action": "ALLOW",
"address_family": "IP4",
"description": "test rule 2",
"direction": "IN",
"enabled": True,
"fields": [
{
"name": "image_name",
"value": "",
"type": "windows_path",
"values": []
}
],
"fqdn": "",
"fqdn_enabled": False,
"icmp": {"icmp_code": "", "icmp_type": ""},
"local_address": [{"address": "*", "netmask": 0}],
"local_port": [{"end": 2000, "start": 1001}],
"log": False,
"monitor": {"count": "1", "period_ms": "1000000"},
"name": "rule2",
"protocol": "6",
"remote_address": [{"address": "10.0.76.101-104", "netmask": 0}],
"remote_port": [{"end": 2000, "start": 1001}],
"temp_id": "2",
},
]

DIFFS = [
{
"value": RULES[0],
"op": "add",
"path": "/rules/0"
},
{
"value": RULES[1],
"op": "add",
"path": "/rules/1"
},
{
"value": "modified0",
"op": "replace",
"path": "/rules/0/name"
},
{
"value": "modified1",
"op": "replace",
"path": "/rules/1/name"
},
{
"op": "remove",
"path": "/rules/1"
}
]


def create_rule_group(random_string):
"""Create a new rule group and returns a rule group entity ID for following operations."""
print("\n\t\tCREATING NEW RULE GROUP...")

response = mgmt.create_rule_group(
description="test rule group",
enabled=True,
name=f"fw-sample-test-group-{random_string}"
)

rule_group_id = response["body"]["resources"][0]

print(f"API Responded: {response['status_code']}")
print(response["body"])
print("New Rule Group ID: "+rule_group_id)

return rule_group_id


def get_rule_group_details(group_id):
"""Retrieve details of new rule group created at the start of execution.
Returns tracking number required for modifications.
"""
print("\n\t\tGETTING NEW RULE GROUP DETAILS...")

response = mgmt.get_rule_groups(group_id)
tracking_number = response["body"]["resources"][0]["tracking"]
ids = response["body"]["resources"][0]["rule_ids"]

print(f"API Responded: {response['status_code']}")
print(response["body"])
print(f"Tracking Number: {tracking_number}")

return tracking_number, ids


def add_new_rule(group_id, tracking_number, rule_id_buffer, i):
"""Add a new rule to the new rule group.
This rule is stored in the RULES variable defined at start of script.
"""
print(f"\n\t\tADDING NEW RULE TO GROUP {group_id}...")

if len(rule_id_buffer) < 1:
rule_ids = [str(i+1)]
else:
rule_ids = rule_id_buffer + [(str(i+1))]

response = mgmt.update_rule_group(
id=group_id,
diff_type="application/json-patch+json",
diff_operations=DIFFS[i],
rule_ids=rule_ids,
rule_versions=[1],
comment="updating test rule group",
tracking=tracking_number
)

print(f"API Responded: {response['status_code']}")
print(response["body"])


def reorder_rules(group_id, tracking_number, rule_id_buffer):
"""Reverse rules in new rule group.
Implemented to demonstrate rule-reordering via API.
"""
print(f"\n\t\tREORDERING RULES IN GROUP {group_id}...")

reversed_rules = rule_id_buffer[::-1]
response = mgmt.update_rule_group(
id=group_id,
rule_ids=reversed_rules,
tracking=tracking_number
)

print(f"API Responded: {response['status_code']}")
print(response["body"])


def modify_rules(group_id, tracking_number, rule_id_buffer):
"""Modify existing rules in new rule group.
The difference is defined in the DIFFS variable.
"""
print(f"\n\t\tMODIFYING RULES IN GROUP {group_id}...")

response = mgmt.update_rule_group(
id=group_id,
diff_type="application/json-patch+json",
diff_operations=DIFFS[2:4],
rule_ids=rule_id_buffer,
rule_versions=[2, 2],
comment="modifying test rule group",
tracking=tracking_number
)

print(f"API Responded: {response['status_code']}")
print(response["body"])


def remove_rule(group_id, tracking_number, rule_id_buffer):
"""Remove the first rule from the group."""
print(f"\n\t\tREMOVING RULE[0] IN GROUP {group_id}...")

trimmed_ids = rule_id_buffer[:1]

response = mgmt.update_rule_group(
id=group_id,
diff_type="application/json-patch+json",
diff_operations=DIFFS[4],
rule_ids=trimmed_ids,
rule_versions=[0, 2],
comment="removing rule 1",
tracking=tracking_number
)

print(f"API Responded: {response['status_code']}")
print(response["body"])


def remove_rule_group(rule_group_id):
"""Remove the rule group first created in this script."""
print(f"\n\t\tREMOVING RULE GROUP: {rule_group_id}...")

response = mgmt.delete_rule_groups(ids=rule_group_id)

print(f"API Responded: {response['status_code']}")
print(response["body"])


def parse_command_line():
"""Parse the passed command line and returns the created Namespace object."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-k", "--key",
help="Falcon API Client ID",
default=os.getenv("FALCON_CLIENT_ID")
)
parser.add_argument("-s", "--secret",
help="Falcon API Client secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)

parsed = parser.parse_args()
if not parsed.key or not parsed.secret:
parser.error(
"You must provide valid API credentials ('-k' and '-s') in order to use this program."
)

return parsed


def main():
"""Execute all the methods defined in this script to demonstrate firewall management APIs."""
random_string = SystemRandom().randrange(1, 10000)
new_rule_group_id = create_rule_group(random_string)

tracking_number, rule_id_buffer = get_rule_group_details(new_rule_group_id)
add_new_rule(new_rule_group_id, tracking_number, rule_id_buffer, 0)

tracking_number, rule_id_buffer = get_rule_group_details(new_rule_group_id)
add_new_rule(new_rule_group_id, tracking_number, rule_id_buffer, 1)

tracking_number, rule_id_buffer = get_rule_group_details(new_rule_group_id)
reorder_rules(new_rule_group_id, tracking_number, rule_id_buffer)

tracking_number, rule_id_buffer = get_rule_group_details(new_rule_group_id)
modify_rules(new_rule_group_id, tracking_number, rule_id_buffer)

tracking_number, rule_id_buffer = get_rule_group_details(new_rule_group_id)
remove_rule(new_rule_group_id, tracking_number, rule_id_buffer)
get_rule_group_details(new_rule_group_id)

remove_rule_group(new_rule_group_id)


# Retrieve our provided command line arguments
args = parse_command_line()
client_id = args.key
client_secret = args.secret
mgmt = FirewallManagement(client_id=client_id, client_secret=client_secret)


if __name__ == "__main__":
main()
3 changes: 2 additions & 1 deletion samples/flight_control/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon Flight Control (MSSP) examples
907 changes: 904 additions & 3 deletions samples/hosts/README.md

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions samples/hosts/bulk_add_falcon_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
__ __ ___ _____ ______ _____ ______ ____ ____ ____ ___ ____
| T T / \ / ___/| T/ ___/ | T / T / T / T / _]| \
| l |Y Y( \_ | ( \_ | |Y o |Y __jY __j / [_ | D )
| _ || O | \__ Tl_j l_j\__ T l_j l_j| || T || T |Y _]| /
| | || | / \ | | | / \ | | | | _ || l_ || l_ || [_ | \
| | |l ! \ | | | \ | | | | | || || || T| . Y
l__j__j \___/ \___j l__j \___j l__j l__j__jl___,_jl___,_jl_____jl__j\_j
This script was developed by @Don-Swanson-Adobe to bulk assign or remove a Falcon
Grouping Tag against a list of hosts based on their serial number.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from falconpy import APIHarnessV2
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-f", "--serial_file",
help="Text file contain serial numbers of hosts to tag",
default="serials.txt"
)
parser.add_argument("-t", "--tag", help="String to use for the Falcon Tag", default="TEST_TAG")
parser.add_argument("-r", "--remove",
help="Remove tag instead of applying it",
action="store_true",
default=False
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


def get_host_serials(target_file: str):
"""Open CSV and import serials."""
try:
with open(target_file, newline='') as serial_file:
print("Opening serial file")
return serial_file.read().split("\n")

except FileNotFoundError:
raise SystemExit(
"You must provide a valid serial file with the '-f' argument in order to run this program."
)


def process_hosts(numbers: list):
"""Process the retrieved serial numbers and apply the tags."""
# Get AID and assign tag for each serial
print("Getting AID")
max_query_terms = 20 # Maximum number of FQL query terms allowed
host_batches = [numbers[i:i+max_query_terms] for i in range(0, len(numbers), max_query_terms)]
for host_batch in host_batches: # Loop through each batch of 1-20
filter_string = ""
for host in [h for h in host_batch if h]: # And create a host filter that includes all hosts
filter_string = f"{filter_string},serial_number:'{host}'"
filter_string = filter_string[1:]

response = falcon.command("QueryDevicesByFilterScroll", filter=filter_string)
if len(response["body"]["resources"]):
aids = response["body"]["resources"]
action = "add" if not cmd_line.remove else "remove"
BODY = {"action": action, "device_ids": aids, "tags": [grouping_tag]}
response = falcon.command("UpdateDeviceTags", body=BODY)
for result in response["body"]["resources"]:
if result["updated"]:
act_stub = "added" if action == "add" else "removed"
print(
f"Successfully {act_stub} {result['device_id']} with {grouping_tag}."
)
else:
print(f"Unable to apply Falcon Grouping Tag to {result['device_id']}.")
else:
print("No results found")

# Consume the command line
cmd_line = consume_arguments()
# Activate API debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)
# Retrieve the list of serial numbers to process
serials = get_host_serials(cmd_line.serial_file)
# Setup the Falcon Grouping Tag
grouping_tag = "FalconGroupingTags/" + cmd_line.tag
# Login to the CrowdStrike Falcon API
falcon = APIHarnessV2(client_id=cmd_line.client_id,
client_secret=cmd_line.client_secret,
debug=cmd_line.debug
)
# Apply the requested tagging changes
process_hosts(serials)
155 changes: 155 additions & 0 deletions samples/hosts/default_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
·▄▄▄▄ ▄▄▄ .·▄▄▄ ▄▄▄· ▄• ▄▌▄▄▌ ▄▄▄▄▄ ▄▄ • ▄▄▄ ▄• ▄▌ ▄▄▄·.▄▄ ·
██▪ ██ ▀▄.▀·▐▄▄·▐█ ▀█ █▪██▌██• •██ ▐█ ▀ ▪▀▄ █·▪ █▪██▌▐█ ▄█▐█ ▀.
▐█· ▐█▌▐▀▀▪▄██▪ ▄█▀▀█ █▌▐█▌██▪ ▐█.▪ ▄█ ▀█▄▐▀▀▄ ▄█▀▄ █▌▐█▌ ██▀·▄▀▀▀█▄
██. ██ ▐█▄▄▌██▌.▐█ ▪▐▌▐█▄█▌▐█▌▐▌ ▐█▌· ▐█▄▪▐█▐█•█▌▐█▌.▐▌▐█▄█▌▐█▪·•▐█▄▪▐█
▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ .▀▀▀ ▀▀▀ ·▀▀▀▀ .▀ ▀ ▀█▄▀▪ ▀▀▀ .▀ ▀▀▀▀
This script was developed to setup the default groups in a new CID.
It should be run once to create the necessary groups and populate
them with the appropriate assignment rules.
Note: This sample demonstrates pythonic response handling using the
Advanced Uber Class (APIHarnessV2).
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2, APIError


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="Create groups for all child CIDs (MSSP parents only).",
action="store_true",
default=False
)
parser.add_argument("-c", "--child",
help="Create groups in a specific child CID (MSSP parents only).",
default=None
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


def add_group(sdk: APIHarnessV2, name: str, rule: str):
"""Add the group the the CID."""
if rule == "staticByID":
BODY = {"resources": [{
"group_type": "staticByID",
"name": name
}]}
elif rule == "none":
BODY = {"resources": [{
"group_type": "dynamic",
"name": name
}]}
else:
BODY = {"resources": [{
"group_type": "dynamic",
"name": name,
"assignment_rule": rule
}]}
try:
sdk.command("createHostGroups", body=BODY)
print(f"{name} group created successfully")
except APIError as api_error:
print(api_error.message)



#### UPDATE THE FOLLOWING DICTIONARY TO MATCH YOUR ENVIRONMENT ##########
# One group will be created for each dictionary item.
# Groups are defined as "Group Name": "Assignment Rule"
groups = {
"Sensor Uninstall Group": "staticByID",
"Phase 0": "none",
"Phase 1": "hostname:*'*'",
"Active Policy": "none",
"Windows Servers": "platform_name:'Windows'+product_type_desc:'Server'",
"DEV Updates": "tags:'SensorGroupingTags/DEV'",
"Golden Images": "tags:'FalconGroupingTags/GoldenImage'",
"Windows 7 and Server 2008 R2 Hosts": "(os_version:'Windows Server 2008 R2',os_version:'Windows 7')"
}
#########################################################################

# Consume the command line
cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our base authentication dictionary (parent / child)
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug,
"pythonic": True
}
# If we are in MSSP mode, retrieve our child CID details
if cmd_line.mssp:
parent = APIHarnessV2(**auth)
cids = parent.command("getChildren", ids=parent.command("queryChildren").data)
elif cmd_line.child:
parent = APIHarnessV2(**auth)
try:
cid_name = parent.command("getChildren", ids=cmd_line.child)
except APIError as api_error:
# Throw an error if they provided us an invalid CID
raise SystemExit(api_error.message)
cids = [{"name": cid_name[0]["name"]}]
else:
# If not, we'll just run this in our current tenant
cids = [{"name": "CrowdStrike"}]

# Do the needful for each CID
for cid in cids:
print(f"Processing {cid['name']}")
if cmd_line.mssp:
# If we're a parent, add this child's CID to our authentication request
auth["member_cid"] = cid["child_cid"]
elif cmd_line.child:
auth["member_cid"] = cmd_line.child
# Authenticate to the API
falcon = APIHarnessV2(**auth)
# Add groups with variable names dependent on CID Name
# (Useful for at a glance reporting of All Hosts and RFM Hosts)
groups.update({
cid["name"] + " - All": "hostname:*'*'",
cid["name"] + " - RFM": "reduced_functionality_mode:'yes'"
})

# Create the groups in the CID
for name,rule in groups.items():
add_group(falcon, name, rule)
117 changes: 117 additions & 0 deletions samples/hosts/get_host_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
__ __ _______ _______ _______
| | | || || || |
| |_| || _ || _____||_ _|
| || | | || |_____ | |
| || |_| ||_____ | | |
| _ || | _____| | | |
|__| |__||_______||_______| |___|
_______ ______ _______ __ __ _______ _______
| || _ | | || | | || || |
| ___|| | || | _ || | | || _ || _____|
| | __ | |_||_ | | | || |_| || |_| || |_____
| || || __ || |_| || || ___||_____ |
| |_| || | | || || || | _____| |
|_______||___| |_||_______||_______||___| |_______|
This script will output a list of all Host Groups, for Flight Control
scenarios it will display all the host groups in all child CIDs.
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2, APIError
from tabulate import tabulate


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="List groups in all child CIDs (MSSP parents only)",
action="store_true",
default=False
)
parser.add_argument("-c", "--child",
help="List groups in a specific child CID (MSSP parents only)",
default=None
)
parser.add_argument("-t", "--table_format",
help="Table format to use for tabular display",
default="simple"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our base authentication dictionary (parent / child)
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug,
"pythonic": True
}
# If we are in MSSP mode, retrieve our child CID details
if cmd_line.mssp:
parent = APIHarnessV2(**auth)
cids = parent.command("getChildren", ids=parent.command("queryChildren").data)
elif cmd_line.child:
parent = APIHarnessV2(**auth)
try:
cid_name = parent.command("getChildren", ids=cmd_line.child)
except APIError as api_error:
# Throw an error if they provided us an invalid CID
raise SystemExit(api_error.message)
cids = [{"name": cid_name[0]["name"]}]
else:
# If not, we'll just run this in our current tenant
cids = [{"name": "CrowdStrike"}]

# Do the needful for each CID in the list
for cid in cids:
print(f"\n{cid['name']} host groups")
if cmd_line.mssp:
# If we're a parent, add this child's CID to our authentication request
auth["member_cid"] = cid["child_cid"]
elif cmd_line.child:
auth["member_cid"] = cmd_line.child
# Demonstrating using the SDK interface as a context manager
# This will automatically discard the bearer token when exiting the context.
with APIHarnessV2(**auth) as sdk:
# Fields we want to display
keep = {"id": "ID", "name": "Name", "description": "Description"}
# Sometimes list comprehension is ridiculously cool...
results = [{k: v for k, v in d.items() if k in keep} for d in sdk.command("queryCombinedHostGroups")]
print(tabulate(tabular_data=results, headers=keep, tablefmt=cmd_line.table_format))
136 changes: 136 additions & 0 deletions samples/hosts/host_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
_ _ _ _____ _
| | | | | | / ___| | |
| |_| | ___ ___| |_ \ `--. ___ __ _ _ __ ___| |__
| _ |/ _ \/ __| __| `--. \/ _ \/ _` | '__/ __| '_ \
| | | | (_) \__ \ |_ /\__/ / __/ (_| | | | (__| | | |
\_| |_/\___/|___/\__| \____/ \___|\__,_|_| \___|_| |_|
This script will take a file listing of hostnames (one host per line) or
a single hostname provided at runtime to produce a CSV containing the
details for hosts that are found. This solution can be used to compare a
list of hostnames to the list of hosts in the Falcon Console to determine
which hostnames are not currently reporting in to the console.
Developed by @Don-Swanson-Adobe
Modification: 05.28.24 - David M. Berry - Updated get_hostnames function to ignore comments.
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import Hosts


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-f", "--hostname_file",
help="Text file containing hostnames to search for",
default="hostnames.txt"
)
parser.add_argument("-n", "--hostname",
help="Hostname to search for",
default=None
)
parser.add_argument("-o", "--output_path",
help="Location to store CSV output",
default="output.csv"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


def get_hostnames(target_file: str):
"""Open file and import hostnames, ignoring comments."""
try:
with open(target_file, 'r') as host_file:
print("Opening hostname file")
hostnames = []
for line in host_file:
line = line.split('#')[0].strip() # Remove comments and strip whitespace
if line: # Ignore empty lines
hostnames.append(line)
return hostnames
except FileNotFoundError:
raise SystemExit(
"You must provide a valid hostname file with the '-f' argument, "
"or a host with the '-n' argument in order to run this program."
)


cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our list of hostnames to search for
hostnames = []
if not cmd_line.hostname:
# They didn't give us a hostname, look for a hostname file
hostnames.extend(get_hostnames(cmd_line.hostname_file))
else:
hostnames.append(cmd_line.hostname)

# Setup constants and successfully identified hostname list
BATCH_SIZE = 250
found = []

# Connect to the API using a context handler for automatic logout and enable pythonic responses
with Hosts(client_id=cmd_line.client_id,
client_secret=cmd_line.client_secret,
debug=cmd_line.debug,
pythonic=True
) as falcon:
# Use a context handler to open our file so closing it later happens automagically
with open(cmd_line.output_path, 'a+') as file_object:
file_object.write("Hostname,CID,RFM,Last Seen,Landscape,Tag1,Tag2,Tag3,Tag4\n")
for batch in [hostnames[i:i+BATCH_SIZE] for i in range(0, len(hostnames), BATCH_SIZE)]:
# Get AIDs for matching hostnames. Exact matches only.
host_ids = falcon.query_devices_by_filter(filter=f"hostname:{batch}")
if host_ids:
# Get and write details for those hosts (use the data parameter iterable)
for device in falcon.get_device_details(ids=host_ids.data):
hostname = device["hostname"]
last_seen = device["last_seen"]
rfm = device["reduced_functionality_mode"]
cid = device["cid"]
tags = device["tags"]
tag = ""
for tag_string in tags:
tag += tag_string.replace('SensorGroupingTags/','')
tag += ","
file_object.write(hostname+","+cid+","+rfm+","+last_seen+","+tag+"\n")
found.append(hostname)
# Compare to the original list
not_found = [x for x in hostnames if x not in set(found)]
for host in not_found:
file_object.write(host+",HOST NOT FOUND\n")

print(f"Search complete, results have been written to {cmd_line.output_path}")
137 changes: 137 additions & 0 deletions samples/hosts/host_search_advanced.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
_ _ _ ____ _
| | | | ___ ___| |_ / ___| ___ __ _ _ __ ___| |__
| |_| |/ _ \/ __| __| \___ \ / _ \/ _` | '__/ __| '_ \
| _ | (_) \__ \ |_ ___) | __/ (_| | | | (__| | | |
|_| |_|\___/|___/\__| |____/ \___|\__,_|_| \___|_| |_|
_ _ _
/ \ __| |_ ____ _ _ __ ___ ___ __| |
/ _ \ / _` \ \ / / _` | '_ \ / __/ _ \/ _` |
/ ___ \ (_| |\ V / (_| | | | | (_| __/ (_| |
/_/ \_\__,_| \_/ \__,_|_| |_|\___\___|\__,_|
This script will take a file listing of hostnames (one host per line) or
a single hostname provided at runtime to produce a CSV containing the
details for hosts that are found. This solution can be used to compare a
list of hostnames to the list of hosts in the Falcon Console to determine
which hostnames are not currently reporting in to the console, or to discover hosts based on a partial match of the hostname. Comments in input files are also ommitted from lookup, thus keeping the output.csv clean, and allowing you to work with more useful host name files/inventory.
Developed by @Don-Swanson-Adobe, additional functionality by @David-M-Berry
"""


import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import Hosts


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-n", "--hostname",
help="Hostname to search for",
required=False # Make this argument optional
)
parser.add_argument("-i", "--input_file", # Add a new argument for input file
help="Text file containing hostnames to search for"
)
parser.add_argument("-o", "--output_path",
help="Location to store CSV output",
default="output.csv"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


def process_hostnames(hostnames):
with open(cmd_line.output_path, 'w') as file_object: # Open in 'write' mode to clear previous content
with Hosts(client_id=cmd_line.client_id,
client_secret=cmd_line.client_secret,
debug=cmd_line.debug,
pythonic=True
) as falcon:
for hostname in hostnames:
host_ids = falcon.query_devices_by_filter(filter=f"hostname:*'*{hostname}*'")
if host_ids:
for device in falcon.get_device_details(ids=host_ids.data):
device_hostname = device["hostname"]
last_seen = device["last_seen"]
rfm = device["reduced_functionality_mode"]
cid = device["cid"]
tags = device["tags"]
tag = ",".join(tag_string.replace('SensorGroupingTags/', '') for tag_string in tags)
file_object.write(f"{device_hostname},{cid},{rfm},{last_seen},{tag}\n")
else:
file_object.write(f"{hostname},HOST NOT FOUND\n")

# Deduplicate the output file
deduplicate_output(cmd_line.output_path)
print(f"Search complete, results have been written to {cmd_line.output_path}")


def deduplicate_output(output_path):
"""Remove duplicate entries from the output file."""
with open(output_path, 'r') as file:
lines = file.readlines()
unique_lines = set(lines)
with open(output_path, 'w') as file:
file.writelines(unique_lines)


def prepend_header(output_path):
"""Prepend the header line to the output file."""
with open(output_path, 'r+') as file:
content = file.read()
file.seek(0, 0)
file.write("Hostname,CID,RFM,Last Seen,Landscape,Tag1,Tag2,Tag3,Tag4\n" + content)


cmd_line = consume_arguments()

if cmd_line.hostname:
hostnames = [cmd_line.hostname]
elif cmd_line.input_file:
with open(cmd_line.input_file, 'r') as file:
hostnames = []
for line in file:
line = line.split('#')[0].strip() # Remove comments and strip whitespace
if line: # Ignore empty lines
hostnames.append(line)
else:
print("You must provide either a hostname or an input file.")
exit(1)


if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

process_hostnames(hostnames)
prepend_header(cmd_line.output_path)
133 changes: 133 additions & 0 deletions samples/hosts/hosts_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
_ _ ______ ______ _______ ______
| | | | / | | \ / | | | / |
| |--| | | | | | '------. | | '------.
|_| |_| \_|__|_/ ____|_/ |_| ____|_/
______ ______ ______ ______ ______ _______
| | | \ | | | | | \ / | | \ | | | \ | |
| |__| | | |---- | |__|_/ | | | | | |__| | | |
|_| \_\ |_|____ |_| \_|__|_/ |_| \_\ |_|
This script was developed by @Don-Swanson-Adobe and is intended to
replace the manual daily export of hosts from the Falcon Console that
was required to audit host compliance. It was developed to be run as
a recurring job and will output a CSV with all hosts in the CID along
with other required info that can then be imported into a compliance
dashboard or tool.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from datetime import datetime
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2

#Function to get detail from the detail_response
def get_detail(detail, filter):
if filter in detail:
return detail[filter]
else:
return "Not Found"


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-o", "--output_path",
help="Location to store CSV output",
default="Hosts_output.csv"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


#Login and run this puppy!
startTime = datetime.now()
cmd_line = consume_arguments()
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)
falcon = APIHarnessV2(client_id=cmd_line.client_id,
client_secret=cmd_line.client_secret,
debug=cmd_line.debug
)
#Setup Outfile
file_object = open(cmd_line.output_path, 'a+')
file_object.write("Hostname,Last Seen,First Seen,Platform,OS Version,OS Build,OS Product Name,Kernel Version,Model,Manufacturer,Type,Chassis,Last Reboot,Prevention Policy,Response Policy,Sensor Update Policy,USB Device Policy,Host ID,MAC Address,Connection MAC Address,Status,CPUID,Serial Number,Sensor Version,Sensor Tags\n")
offset = ''
response = falcon.command("QueryDevicesByFilterScroll")
total = response["body"]["meta"]["pagination"]["total"]

while total > 0:
response = falcon.command("QueryDevicesByFilterScroll", offset=offset, limit=5000)
print("Total Remaining: ",total)
total = total - 5000
offset = response["body"]["meta"]["pagination"]["offset"]
detail_response = falcon.command("GetDeviceDetails", ids=response["body"]["resources"])
for detail in detail_response["body"]["resources"]:
hostname = get_detail(detail, "hostname")
last_seen = get_detail(detail, "last_seen")
first_seen = get_detail(detail, "first_seen")
platform = get_detail(detail, "platform_name")
os_version = get_detail(detail, "os_version")
os_build = get_detail(detail, "os_build")
os_product_name = get_detail(detail, "os_product_name")
kernel_version = get_detail(detail, "kernel_version")
model = get_detail(detail, "system_product_name").replace(",", " ")
manufacturer = get_detail(detail, "system_manufacturer").replace(",", " ")
type = get_detail(detail, "product_type_desc")
chassis = get_detail(detail, "chassis_type_desc")
last_reboot = get_detail(detail, "last_reboot")
if "device_policies" in detail:
prevention_policy = detail['device_policies']['prevention']['policy_id']
response_policy = detail['device_policies']['remote_response']['policy_id']
sensor_update_policy = detail['device_policies']['sensor_update']['policy_id']
if "usb_storage_control" in detail['device_policies']:
usb_device_policy = detail['device_policies']['usb_storage_control']['policy_id']
else:
usb_device_policy = "Not Found"
else:
prevention_policy = "Not Found"
response_policy = "Not Found"
sensor_update_policy = "Not Found"
usb_device_policy = "Not Found"
host_id = get_detail(detail, "device_id")
mac_address = get_detail(detail, "mac_address")
connection_mac_address = get_detail(detail, "connection_mac_address")
status = get_detail(detail, "status")
cpuid = get_detail(detail, "cpu_signature")
serial_number = get_detail(detail, "serial_number")
sensor_version = get_detail(detail, "agent_version")
sensor_tags = (str(get_detail(detail, "tags")).replace(",", ";"))
file_object.write(hostname+","+last_seen+","+first_seen+","+platform+","+os_version+","+os_build+","+os_product_name+","+kernel_version+","+model+","+manufacturer+","+type+","+chassis+","+last_reboot+","+prevention_policy+","+response_policy+","+sensor_update_policy+","+usb_device_policy+","+host_id+","+mac_address+","+connection_mac_address+","+status+","+cpuid+","+serial_number+","+sensor_version+","+sensor_tags+"\n")

file_object.close()
print("Done")
print("Time to complete: ",datetime.now() - startTime)
137 changes: 137 additions & 0 deletions samples/hosts/policy_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
__ ___ ___ ___
.'|=| | .'|=|`. .'| .'| .'|=|_.' | | | |
.' | | | .' | | `. .' | .' | .' | `. |_| .'
| |=|.' | | | | | | | | | | `. .'
| | `. | | .' | | ___ | | `. | ___ | |
|___| `.|=|.' |___|=|_.' |___| `.|=|_.' |___|
___ ___ ___
.'|=|_.' .'| |`. .'|=|_.' .'|=|_.' .'| .'|
.' | .' | | `. .' | ___ .' | .' | .' .'
| | | |=| | | |=|_.' | | | |=|.:
`. | ___ | | | | | | ___ `. | ___ | | |'.
`.|=|_.' |___| |___| |___|=|_.' `.|=|_.' |___| |_|
This program will check if a specific host group is properly
assigned to a list of Prevention Policies.
Created by: @Don-Swanson-Adobe
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2, APIError
from termcolor import colored

def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="List groups in all child CIDs (MSSP parents only)",
action="store_true",
default=False
)
parser.add_argument("-c", "--child",
help="List groups in a specific child CID (MSSP parents only)",
default=None
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
req.add_argument("-g", "--group_name",
help="Group name to check",
required=True
)
req.add_argument("-p", "--policy_ids",
help="Policy IDs to confirm (comma delimit)",
required=True
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


# Let's shine up the screen output a bit with a couple of constants
THERE = colored("is", "green", attrs=["bold"])
NOT_THERE = colored("is NOT", "red", attrs=["bold"])

# Consume any command line arguments
cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our base authentication dictionary (parent / child)
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug,
"pythonic": True
}

# If we are in MSSP mode, retrieve our child CID details
if cmd_line.mssp:
parent = APIHarnessV2(**auth)
cids = parent.command("getChildren", ids=parent.command("queryChildren").data)
elif cmd_line.child:
parent = APIHarnessV2(**auth)
try:
cid_name = parent.command("getChildren", ids=cmd_line.child)
except APIError as api_error:
# Throw an error if they provided us an invalid CID
raise SystemExit(api_error.message)
cids = [{"name": cid_name[0]["name"], "child_cid": cmd_line.child}]
else:
# If not, we'll just run this in our current tenant
cids = [{"name": "CrowdStrike"}]

for cid in cids:
print(f"CID: {cid['name']}")
if cmd_line.mssp or cmd_line.child:
auth["member_cid"] = cid["child_cid"]
# Open the SDK using a context manager so it automatically logs us out when we're done
with APIHarnessV2(**auth) as sdk:
# Parse thru the groups available to identify our match
# This will return a list of one dictionary on success
gid = [g for g in sdk.command("queryCombinedHostGroups").data if g["name"] == cmd_line.group_name]
if gid:
# Check for bad policy ID input
try:
policies = sdk.command("getPreventionPolicies", ids=cmd_line.policy_ids).data
except APIError as api_error:
# Stop processing if the policy IDs are not valid
raise SystemExit(f"One or more of the policy IDs provided is invalid ({api_error.message}).")
for policy in policies:
# Use a quick comprehension to identify if there is a match, empty list = no match
status = THERE if [g for g in policy["groups"] if g["id"] == gid[0]["id"]] else NOT_THERE
print(
f"The {colored(cmd_line.group_name, 'white', attrs=['bold'])} group "
f"{status} assigned to the {colored(policy['name'], 'white', attrs=['underline'])} "
f"({policy['id']}) [{policy['platform_name']}] policy."
)
else:
print(f"The {cmd_line.group_name} group does not exist within {cid['name']}.")
130 changes: 130 additions & 0 deletions samples/hosts/rfm_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
██████ ███████ ███ ███ ██████ ███████ ██████ ██████ ██████ ████████
██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ █████ ██ ████ ██ ██████ █████ ██████ ██ ██ ██████ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ███████ ██ ██████ ██ ██ ██
This script was developed by developed by @Don-Swanson-Adobe to determine the
number of hosts in RFM (Up for more than 24 hours and seen within the last 24
hours) in your tenant or every child tenant attached to your parent.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from datetime import datetime, timedelta
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="Return RFM details for child CIDs (MSSP parents only).",
action="store_true",
default=False
)
parser.add_argument("-o", "--output_path",
help="Location to store CSV output",
default="RFM_Report.csv"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


# Consume the command line
cmd_line = consume_arguments()
if cmd_line.debug:
# Activate debugging if requested
logging.basicConfig(level=logging.DEBUG)

# Establish 24 hours ago as the last seen time, setup the output file,
# and the variable to store which serial numbers we found
last = (datetime.now() - timedelta(hours = 24)).strftime('%Y-%m-%dT%H:%M:%SZ')
file_object = open(cmd_line.output_path, 'a+')
file_object.write("CID,Hostname,AID,Last Seen,RFM,OS,Kernel,Sensor Version,Tag1,Tag2,Tag3,Tag4\n")
filter_string = f"last_seen:>='{last}'+first_seen:<='{last}'+reduced_functionality_mode:'yes'"
rfm_total=0
if cmd_line.mssp:
# Retrieve the list of child CIDs if we're in MSSP mode
parent = APIHarnessV2(client_id=cmd_line.client_id,
client_secret=cmd_line.client_secret,
debug=cmd_line.debug
)
cids = parent.command("queryChildren")["body"]["resources"]
else:
# Just check my tenant
cids = ["My tenant"]

# Do the needful for each CID in the list
for key in cids:
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug
}
if key != "My tenant":
auth["member_cid"] = key
falcon = APIHarnessV2(**auth)
response = falcon.command("QueryDevicesByFilter", filter=filter_string)
if "resources" in response['body']:
total = response["body"]["meta"]["pagination"]["total"]
rfm_total += total
off = 0
while total > 0:
response = falcon.command("QueryDevicesByFilter",
offset=off,
limit=500,
sort="device_id.asc",
filter=filter_string
)
aids = response["body"]["resources"]
hosts = falcon.command("GetDeviceDetails", ids=aids)["body"]["resources"]
if hosts is not None:
for info in hosts:
hostname = (info["hostname"])
aid = (info["device_id"])
last_seen = (info["last_seen"])
rfm = (info["reduced_functionality_mode"])
os_version = (info["os_version"])
kernel = (info["kernel_version"])
tags = (info["tags"])
agent_version = (info["agent_version"])
tag = ""
for t in tags:
tag += t.replace('SensorGroupingTags/','')
tag += ","
file_object.write(key+","+hostname+","+aid+","+last_seen+","+rfm+","+os_version+","+kernel+","+agent_version+","+tag+"\n")
total -= 500
off += 500

file_object.close()
print(f"Total hosts identified as in Reduced Functionality Mode: {rfm_total}")
145 changes: 145 additions & 0 deletions samples/hosts/serial_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
8¯¯¯¯8 8¯¯¯¯8
8 eeee eeeee e eeeee e 8 eeee eeeee eeeee eeee e e
8eeeee 8 8 8 8 8 8 8 8eeeee 8 8 8 8 8 8 8 8 8
88 8eee 8eee8e 8e 8eee8 8e 88 8eee 8eee8 8eee8e 8e 8eee8
e 88 88 88 8 88 88 8 88 e 88 88 88 8 88 8 88 88 8
8eee88 88ee 88 8 88 88 8 88eee 8eee88 88ee 88 8 88 8 88e8 88 8
This script takes a file listing Serial Numbers and outputs a CSV with the
Serial Number, Hostname, CID, RFM, Last Seen, Local IP, and Tags for each
host in the list. This list can be used to compare a list of serial numbers
to the list of hosts in the Falcon Console to determine which serial numbers
are not currently reporting to the console.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from datetime import datetime, timedelta
from falconpy import Hosts


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-f", "--serial_file",
help="Text file contain serial numbers of hosts to tag",
default="serials.txt"
)
parser.add_argument("-o", "--output_path",
help="Location to store CSV output",
default="output.csv"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


def get_host_serials(target_file: str):
"""Open CSV and import serials."""
try:
with open(target_file, newline='') as serial_file:
print("Opening serial file")
return serial_file.read().splitlines()

except FileNotFoundError:
raise SystemExit(
"You must provide a valid serial file with the '-f' argument in order to run this program."
)


def diff_compare(source, found):
"""Function to determine which serial numbers couldn't be found."""
s = set(found)
li_dif = [x for x in source if x not in s]
return li_dif


cmd_line = consume_arguments()
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Setup the current last seen time as the last 72 hours
lastseen = (datetime.utcnow() - timedelta(hours = 72)).strftime('%Y-%m-%dT%H:%M:%SZ')

# Retrieve the list of serial numbers to process
serials = get_host_serials(cmd_line.serial_file)

# Chunk the list of serials into a usable size
def chunker(seq, size):
return (seq[pos:pos + size] for pos in range(0, len(seq), size))

# Setup the output file, and the variable to store which serial numbers we found
SORT = "hostname.asc"
file_object = open(cmd_line.output_path, 'a+')
file_object.write("Serial,Hostname,CID,RFM,Last Seen,Local IP,Tag1,Tag2,Tag3,Tag4\n")
found = []

# Setup the API Harness
falcon = Hosts(client_id=cmd_line.client_id,
client_secret=cmd_line.client_secret,
debug=cmd_line.debug
)

# Chunk the Serial numbers into groups of 250
for group in chunker(serials, 250):
# Get AIDs for matching hosts
host_ids = falcon.query_devices_by_filter(filter=f"serial_number:{group}")

# Get and write details for those hosts.
devices = falcon.get_device_details(ids=host_ids["body"]["resources"])["body"]["resources"]
if devices is not None:
for device in devices:
if len(device):
if device["last_seen"] >= lastseen:
hostname = device["hostname"]
last_seen = device["last_seen"]
rfm = device["reduced_functionality_mode"]
cid = device["cid"]
serial = device["serial_number"]
if "local_ip" in device:
local_ip = device["local_ip"]
else:
local_ip = "Unavailable"
tags = device["tags"]
tag = ""
for i in tags:
tag += i.replace('SensorGroupingTags/','')
tag += ","
file_object.write(serial+","+hostname+","+cid+","+rfm+","+last_seen+","+local_ip+","+tag+"\n")
found.append(serial)

# Compare to original list to determine which couldn't be found
print("Determining Hosts Not Found")
not_found = diff_compare(serials,found)
for host in not_found:
file_object.write(host+",Serial NOT FOUND\n")

file_object.close()
56 changes: 37 additions & 19 deletions samples/hosts/stale_sensors.py
Original file line number Diff line number Diff line change
@@ -21,8 +21,12 @@
- @morcef, jshcodes@CrowdStrike; 06.05.22 - More reasonable date calcs, Linting, Easier arg parsing
Easier base_url handling, renamed grouping_tag to tag
- jshcodes@Crowdstrike; 11.02.22 - Added CSV output options and cleaner date outputs.
- nmills@forbarr; 22.05.24 - Fixed deprecation warning on date function,
Added new arg to accept hostname pattern
Batch the call to hide_hosts to avoid API error
"""
import csv
import re
from argparse import ArgumentParser, RawTextHelpFormatter
from datetime import datetime, timedelta, timezone
from dateutil import parser as dparser
@@ -35,7 +39,6 @@
"Please execute `python3 -m pip install crowdstrike-falconpy` and try again."
) from no_falconpy


def parse_command_line() -> object:
"""Parse command-line arguments and return them back as an ArgumentParser object."""
parser = ArgumentParser(
@@ -74,7 +77,8 @@ def parse_command_line() -> object:
'-d',
'--days',
help='Number of days since a host was seen before it is considered stale',
required=False
required=False,
default=10
)
parser.add_argument(
'-r',
@@ -130,9 +134,15 @@ def parse_command_line() -> object:
required=False,
dest="osfilter"
)
parser.add_argument(
"-p", "--host-pattern",
help="filter hostnames by regex",
default=r".*",
required=False,
dest="hostfilter"
)
return parser.parse_args()


def connect_api(key: str, secret: str, base_url: str, child_cid: str = None) -> Hosts:
"""Connect to the API and return an instance of the Hosts Service Class."""
return Hosts(client_id=key, client_secret=secret, base_url=base_url, member_cid=child_cid)
@@ -156,6 +166,10 @@ def get_hosts(date_filter: str, tag_filter: str, os_filter: str) -> list:
if os_filter == "K8s":
os_filter = "K8S"
filter_string = f"{filter_string} + platform_name:'{os_filter}'"
x = falcon.query_devices_by_filter_scroll(
limit=5000,
filter=filter_string
)["body"]["resources"]
return falcon.query_devices_by_filter_scroll(
limit=5000,
filter=filter_string
@@ -164,7 +178,7 @@ def get_hosts(date_filter: str, tag_filter: str, os_filter: str) -> list:

def calc_stale_date(num_days: int) -> str:
"""Calculate the 'stale' datetime based upon the number of days provided by the user."""
today = datetime.utcnow()
today = datetime.now(timezone.utc)
return str(today - timedelta(days=num_days)).replace(" ", "T")


@@ -212,14 +226,15 @@ def hide_hosts(id_list: list) -> dict:
# List to hold our identified hosts
stale = []
# For each stale host identified
try:
for host in get_host_details(get_hosts(STALE_DATE, args.tag, args.osfilter)):
# Retrieve host detail
stale = parse_host_detail(host, stale)
except KeyError as api_error:
raise SystemExit(
"Unable to communicate with CrowdStrike API, check credentials and try again."
) from api_error
pattern = args.hostfilter
if args.hostfilter != r".*":
print(f"Pattern is: {re.escape(pattern)}")

for host in get_host_details(get_hosts(STALE_DATE, args.tag, args.osfilter)):
# Retrieve host detail
if 'hostname' in host:
if re.findall(pattern, host['hostname']):
stale = parse_host_detail(host, stale)

# If we produced stale host results
if stale:
@@ -245,12 +260,15 @@ def hide_hosts(id_list: list) -> dict:
else:
# Remove the hosts
host_list = [x[1] for x in stale]
remove_result = hide_hosts(host_list)
if remove_result["status_code"] == 202:
for deleted in remove_result["body"]["resources"]:
print(f"Removed host {deleted['id']}")
else:
for deleted in remove_result["body"]["errors"]:
print(f"[{deleted['code']}] {deleted['message']}")
batch_size = 50
for i in range(0, len(host_list), batch_size):
batch = host_list[i:i + batch_size]
remove_result = hide_hosts(batch)
if remove_result["status_code"] == 202:
for deleted in remove_result["body"]["resources"]:
print(f"Removed host {deleted['id']}")
else:
for deleted in remove_result["body"]["errors"]:
print(f"[{deleted['code']}] {deleted['message']}")
else:
print("No stale hosts identified for the range specified.")
3 changes: 2 additions & 1 deletion samples/identity/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Identity Protection examples
3 changes: 2 additions & 1 deletion samples/incidents/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Incidents examples
3 changes: 2 additions & 1 deletion samples/installation_tokens/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)

[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

44 changes: 41 additions & 3 deletions samples/intel/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Intel examples
@@ -416,12 +417,42 @@ Total indicators: 1
Execution time: 2.86 seconds
```
Show the debug output.
```shell
python3 intel_search.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -f "FROZEN SPIDER" -d
```
##### Result
```shell
DEBUG:falconpy._auth_object._falcon_interface:CREATED: OAuth2 interface class
DEBUG:falconpy._auth_object._falcon_interface:AUTH: Configured for Direct Authentication
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Base URL set to https://api.crowdstrike.com
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: SSL verification is set to True
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Timeout set to None seconds
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Proxy dictionary: None
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: User-Agent string set to: None
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Token renewal window set to 120 seconds
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Maximum number of records to log: 100
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Log sanitization is enabled
DEBUG:falconpy._auth_object._falcon_interface:CONFIG: Pythonic responses are disabled
DEBUG:falconpy._auth_object._falcon_interface:OPERATION: oauth2AccessToken
DEBUG:falconpy._auth_object._falcon_interface:ENDPOINT: https://api.crowdstrike.com/oauth2/token (POST)
DEBUG:falconpy._auth_object._falcon_interface:HEADERS: {'User-Agent': 'crowdstrike-falconpy/1.4.4', 'CrowdStrike-SDK': 'crowdstrike-falconpy/1.4.4'}
DEBUG:falconpy._auth_object._falcon_interface:PARAMETERS: None
DEBUG:falconpy._auth_object._falcon_interface:BODY: None
DEBUG:falconpy._auth_object._falcon_interface:DATA: {'client_id': 'REDACTED', 'client_secret': 'REDACTED'}
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.crowdstrike.com:443
```
#### Command-line help
Command-line help is available via the `-h` argument.
```shell
python3 intel_search.py -h
usage: intel_search.py [-h] -f FIND -k CLIENT_ID -s CLIENT_SECRET [-r] [-t TYPES] [-tf TABLE_FORMAT] [-o OUTPUT_PREFIX]
CrowdStrike Falcon Intel API search example using the FalconPy library.
usage: intel_search.py [-h] -f FIND -k CLIENT_ID -s CLIENT_SECRET [-r] [-t TYPES] [-tf TABLE_FORMAT]
[-o OUTPUT_PREFIX] [-d]
CrowdStrike Falcon Intel API search example using the FalconPy library.
@@ -443,7 +474,13 @@ A maximum of 50,000 results per category will be returned.
Creation date: 03.30.23 - jshcodes@CrowdStrike
optional arguments:
This application requires:
pyfiglet
termcolor
tabulate
crowdstrike-falconpy v1.3.0+
options:
-h, --help show this help message and exit
-r, --reverse Reverse the sort.
-t TYPES, --types TYPES
@@ -452,6 +489,7 @@ optional arguments:
Set the table format.
-o OUTPUT_PREFIX, --output_prefix OUTPUT_PREFIX
Output filename prefix for storing results (CSV format).
-d, --debug Enable API debugging
required arguments:
-f FIND, --find FIND Search string to identify
24 changes: 20 additions & 4 deletions samples/intel/intel_search.py
Original file line number Diff line number Diff line change
@@ -18,8 +18,15 @@
A maximum of 50,000 results per category will be returned.
Creation date: 03.30.23 - jshcodes@CrowdStrike
This application requires:
pyfiglet
termcolor
tabulate
crowdstrike-falconpy v1.3.0+
"""

import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from concurrent.futures import ThreadPoolExecutor
from csv import writer, QUOTE_ALL
@@ -109,11 +116,19 @@ def parse_command_line():
help="Output filename prefix for storing results (CSV format).",
default=None
)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)

parsed = parser.parse_args()
allow = ["indicator", "report", "actor"]
parsed.types = [t for t in parsed.types.split(",") if t in allow] if parsed.types else allow

if parsed.debug:
logging.basicConfig(level=logging.DEBUG)

return parsed


@@ -122,6 +137,7 @@ def bold(val: str):
return colored(val, attrs=["bold"])


# pylint: disable=E0606
def batch_get(func: object, filt: str, catg: str):
"""Asynchronously retrieve Falcon Intelligence API results."""
offset = 0
@@ -167,7 +183,7 @@ def chunk_long_description(desc: str, col_width: int = 120) -> str:


def simple_list_display(keyval: str, record: dict, title: str, no_val: bool = False):
"""Generic handler for displaying information provided as simple lists."""
"""Dynamic handler for displaying information provided as simple lists."""
if keyval in record:
if len(record[keyval]):
if no_val:
@@ -178,12 +194,12 @@ def simple_list_display(keyval: str, record: dict, title: str, no_val: bool = Fa


def large_list_display(keyval: str, record: dict, title: str):
"""Generic handler for displaying list information with an underlined header."""
"""Dynamic handler for displaying list information with an underlined header."""
if keyval in record:
if len(record[keyval]):
res = ", ".join(t["value"].title() for t in record[keyval])
res = f"{chunk_long_description(res)}"
res = f"{colored(title, attrs=['bold','underline'])}\n{res}"
res = f"{colored(title, attrs=['bold', 'underline'])}\n{res}"
print(f"{res}\n")


@@ -565,7 +581,7 @@ def show_result_totals(act_cnt: int, ind_cnt: int, rep_cnt: int, typ_list: list)
def main(args: Namespace):
"""Search for a specified string and identify if it matches an indicator, report, or actor."""
# Perform the search using an authenticated instance of the Intel Service Class
ret = perform_search(Intel(client_id=args.client_id, client_secret=args.client_secret),
ret = perform_search(Intel(client_id=args.client_id, client_secret=args.client_secret, debug=args.debug),
args.find, # Search string
args.types, # Types to display
args.table_format, # Table format
127 changes: 127 additions & 0 deletions samples/ioa_exclusions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# IOA Exclusions samples
The examples within this folder focus on leveraging CrowdStrike Falcon IOA Exclusions collection.

- [IOA Audit](#ioa-audit)

## IOA Audit
This program will output a list of IOA exclusions and their details for either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of IOA exclusions across multiple CIDs.

### Running the program
In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes:

| Service Collection | Scope |
| :---- | :---- |
| IOA Exclusions | __READ__ |
| Flight Control | __READ__ |
| Sensor Download | __READ__ |

> [!NOTE]
> This program can be executed using an API key that is not scoped for the Flight Control (MSSP) and Sensor Download service collections, but will be unable to lookup the current CID (Sensor Download) or access child CIDs (Flight Control).
### Execution syntax
This sample leverages simple command-line arguments to implement functionality.

#### Basic usage
Execute the default example. This will output results to a CSV file named `ioa_exclusions.txt`.

```shell
python3 ioa_audit.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET
```

> This sample supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning you can execute any of the command lines shown below without providing credentials if you have the values `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` defined in your environment.
```shell
python3 ioa_audit.py
```

Change the output destination with the `-o` argument.

```shell
python3 ioa_audit.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -o new_ioa_exclusions.txt
```

Enable MSSP mode and audit all Flight Control children with the `-m` argument.

```shell
python3 ioa_audit.py -k $FALCON_CLIENT_ID_PARENT -s $FALCON_CLIENT_SECRET_PARENT -m
```

Enable MSSP mode and audit a specific Flight Control child with the `-c` argument.

```shell
python3 ioa_audit.py -k $FALCON_CLIENT_ID_PARENT -s $FALCON_CLIENT_SECRET_PARENT -c CHILD_CID
```

> API debugging can be enabled using the `-d` argument.
```shell
python3 ioa_audit.py -d
```

#### Command-line help
Command-line help is available via the `-h` argument.

```shell
usage: ioa_audit.py [-h] [-d] [-m] [-c CHILD] [-o OUTPUT_FILE] [-k CLIENT_ID] [-s CLIENT_SECRET]

_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
_____ _______ _____
/\ \ /::\ \ /\ \
/::\ \ /::::\ \ /::\ \
\:::\ \ /::::::\ \ /::::\ \
\:::\ \ /::::::::\ \ /::::::\ \
\:::\ \ /:::/~~\:::\ \ /:::/\:::\ \
\:::\ \ /:::/ \:::\ \ /:::/__\:::\ \
/::::\ \ /:::/ / \:::\ \ /::::\ \:::\ \
____ /::::::\ \ /:::/____/ \:::\____\ /::::::\ \:::\ \
/\ \ /:::/\:::\ \ |:::| | |:::| | /:::/\:::\ \:::\ \
/::\ \/:::/ \:::\____\|:::|____| |:::| |/:::/ \:::\ \:::\____\
\:::\ /:::/ \::/ / \:::\ \ /:::/ / \::/ \:::\ /:::/ /
\:::\/:::/ / \/____/ \:::\ \ /:::/ / \/____/ \:::\/:::/ /
\::::::/ / \:::\ /:::/ / \::::::/ /
\::::/____/ \:::\__/:::/ / \::::/ /
\:::\ \ \::::::::/ / /:::/ /
\:::\ \ \::::::/ / /:::/ /
\:::\ \ \::::/ / /:::/ /
\:::\____\ \::/____/ /:::/ /
\::/ / ¯¯ \::/ /
\/____/ ▄▄▄ █ ▀ \/____/
█▄▄ ▀▄▀ █▀▀ █ █ █ █▀▀ █ █▀█ █▀█ █▀▀
█▄▄ ▄▀▄ █▄▄ █▄ █▄█ ▄▄█ █ █▄█ █ █ ▄▄█
This program will output a list of IOA exclusions and their details for
either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of IOA exclusions across multiple CIDs.
Developed by @Don-Swanson-Adobe
optional arguments:
-h, --help show this help message and exit
-d, --debug Enable API debugging
-m, --mssp List groups in all child CIDs (MSSP parents only)
-c CHILD, --child CHILD
List groups in a specific child CID (MSSP parents only)
-o OUTPUT_FILE, --output_file OUTPUT_FILE
File to output results to
Required arguments:
-k CLIENT_ID, --client_id CLIENT_ID
CrowdStrike Falcon API key
-s CLIENT_SECRET, --client_secret CLIENT_SECRET
CrowdStrike Falcon API secret
```

### Example source code
The source code for this example can be found [here](ioa_audit.py).
163 changes: 163 additions & 0 deletions samples/ioa_exclusions/ioa_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
r"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
_____ _______ _____
/\ \ /::\ \ /\ \
/::\ \ /::::\ \ /::\ \
\:::\ \ /::::::\ \ /::::\ \
\:::\ \ /::::::::\ \ /::::::\ \
\:::\ \ /:::/~~\:::\ \ /:::/\:::\ \
\:::\ \ /:::/ \:::\ \ /:::/__\:::\ \
/::::\ \ /:::/ / \:::\ \ /::::\ \:::\ \
____ /::::::\ \ /:::/____/ \:::\____\ /::::::\ \:::\ \
/\ \ /:::/\:::\ \ |:::| | |:::| | /:::/\:::\ \:::\ \
/::\ \/:::/ \:::\____\|:::|____| |:::| |/:::/ \:::\ \:::\____\
\:::\ /:::/ \::/ / \:::\ \ /:::/ / \::/ \:::\ /:::/ /
\:::\/:::/ / \/____/ \:::\ \ /:::/ / \/____/ \:::\/:::/ /
\::::::/ / \:::\ /:::/ / \::::::/ /
\::::/____/ \:::\__/:::/ / \::::/ /
\:::\ \ \::::::::/ / /:::/ /
\:::\ \ \::::::/ / /:::/ /
\:::\ \ \::::/ / /:::/ /
\:::\____\ \::/____/ /:::/ /
\::/ / ¯¯ \::/ /
\/____/ ▄▄▄ █ ▀ \/____/
█▄▄ ▀▄▀ █▀▀ █ █ █ █▀▀ █ █▀█ █▀█ █▀▀
█▄▄ ▄▀▄ █▄▄ █▄ █▄█ ▄▄█ █ █▄█ █ █ ▄▄█
This program will output a list of IOA exclusions and their details for
either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of IOA exclusions across multiple CIDs.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2, APIError


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="List groups in all child CIDs (MSSP parents only)",
action="store_true",
default=False
)
parser.add_argument("-c", "--child",
help="List groups in a specific child CID (MSSP parents only)",
default=None
)
parser.add_argument("-o", "--output_file",
help="File to output results to",
default="ioa_exclusions.txt"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


# Consume any command line arguments
cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our base authentication dictionary (parent / child)
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug,
"pythonic": True
}
local = APIHarnessV2(**auth)
# If we are in MSSP mode, retrieve our child CID details
if cmd_line.mssp:
try:
cids = local.command("getChildren", ids=local.command("queryChildren").data)
except APIError as api_erorr:
# Assume they do not have access to Flight Control
raise SystemExit("This API client does not have access to the Flight Control API scope.")
if not cids:
raise SystemExit("No child CIDs were found within this tenant.")
elif cmd_line.child:
try:
cid_name = local.command("getChildren", ids=cmd_line.child)
except APIError as api_error:
# Throw an error if they provided us an invalid CID or do not have access to Flight Control
if api_error.code == 403:
raise SystemExit("This API client does not have access to the Flight Control API scope.")
elif api_error.code == 400:
raise SystemExit("Invalid child CID provided.")
else:
raise SystemExit(api_error.message)
if cid_name:
cids = [{"name": cid_name[0]["name"], "child_cid": cmd_line.child}]
else:
raise SystemExit("The provided child CID was not found within this tenant.")
else:
# If not, we'll just run this in our current tenant
try:
cid_id = local.command("GetSensorInstallersCCIDByQuery").data[0][:-3].lower()
except APIError as api_error:
# They do not have access to the sensor downloads service collection with this key
cid_id = f"Sensor Download scope required "
cids = [{"name": "My CrowdStrike tenant",
"child_cid": cid_id
}]

with open(cmd_line.output_file, 'a+') as file_object:
for cid in cids:
if cmd_line.mssp or cmd_line.child:
auth["member_cid"] = cid["child_cid"]
spot = 38 - len(cid["name"])
header = f"\n\n{'*¯'*20}*\n* "+cid["name"]
header = f"{header}{' '*spot}*\n* CID: "+cid["child_cid"]+f" *\n{'*¯'*20}*\n"
print(header)
file_object.write(header)
# Open the API using a context manager so we automatically log out
with APIHarnessV2(**auth) as falcon:
# Query for the list of IOA exclusions in the CID and retrieve the exclusion detail
response = falcon.command("queryIOAExclusionsV1").data
if response:
for detail in falcon.command("getIOAExclusionsV1", ids=response).data:
details = ["Exclusion Name: " + detail["name"],
"Description: " + detail["description"],
"Pattern Name: " + detail["pattern_name"],
"Command Line: " + detail["cl_regex"],
"Creator: " + detail["created_by"],
"Created on: " + detail["created_on"],
"Last Modified by: " + detail["modified_by"],
"Last Modified on: " + detail["last_modified"]
]
print("\n".join(details))
file_object.write("\n".join(details)+"\n")
else:
print("No exclusions found")
file_object.write("No exclusions found\n")
262 changes: 261 additions & 1 deletion samples/ioc/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# Falcon IOC samples
The examples within this folder focus on leveraging CrowdStrike's Falcon IOC API.

- [Create Indicator of Compromise](#create-indicator-of-compromise)
- [IOC Audit](#ioc-audit)
- [IOC Restore](#ioc-restore)

## Create Indicator of Compromise
Demonstrates the creation of a single IOC using either the Service or Uber Class.
@@ -84,3 +88,259 @@ optional arguments:
### Example source code
The source code for this example can be found [here](create_ioc.py).
---
## IOC Audit
This program will output a list of IOCs and their details for either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of IOCs across multiple CIDs.
### Running the program
In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes:
| Service Collection | Scope |
| :---- | :---- |
| IOC | __READ__ |
| Flight Control | __READ__ |
| Sensor Download | __READ__ |
> [!NOTE]
> This program can be executed using an API key that is not scoped for the Flight Control (MSSP) and Sensor Download service collections, but will be unable to lookup the current CID (Sensor Download) or access child CIDs (Flight Control).
### Execution syntax
This sample leverages simple command-line arguments to implement functionality.
#### Basic usage
Execute the default example. This will output results to a CSV file named `iocs.txt`.
```shell
python3 ioc_audit.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET
```
> This sample supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning you can execute any of the command lines shown below without providing credentials if you have the values `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` defined in your environment.
```shell
python3 ioc_audit.py
```
Change the output destination with the `-o` argument.
```shell
python3 ioc_audit.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -o new_iocs.txt
```
Enable MSSP mode and audit all Flight Control children with the `-m` argument.
```shell
python3 ioc_audit.py -k $FALCON_CLIENT_ID_PARENT -s $FALCON_CLIENT_SECRET_PARENT -m
```
Enable MSSP mode and audit a specific Flight Control child with the `-c` argument.
```shell
python3 ioc_audit.py -k $FALCON_CLIENT_ID_PARENT -s $FALCON_CLIENT_SECRET_PARENT -c CHILD_CID
```
> API debugging can be enabled using the `-d` argument.
```shell
python3 ioc_audit.py -d
```
#### Command-line help
Command-line help is available via the `-h` argument.
```shell
usage: ioc_audit.py [-h] [-d] [-m] [-c CHILD] [-o OUTPUT_FILE] [-k CLIENT_ID] [-s CLIENT_SECRET]
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄
▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▄▄▄▄█░█▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄
▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀
▄▄▄ █ ▀
█▄▄ ▀▄▀ █▀▀ █ █ █ █▀▀ █ █▀█ █▀█ █▀▀
█▄▄ ▄▀▄ █▄▄ █▄ █▄█ ▄▄█ █ █▄█ █ █ ▄▄█
This program will output a list of IOCs and their details for either the
current CID or in each Child CID (Flight Control scenarios). This can be
used for regular audits of indicators of compromise across multiple CIDs.
Developed by @Don-Swanson-Adobe
optional arguments:
-h, --help show this help message and exit
-d, --debug Enable API debugging
-m, --mssp List exclusions in all child CIDs (MSSP parents only)
-c CHILD, --child CHILD
List exclusions in a specific child CID (MSSP parents only)
-o OUTPUT_FILE, --output_file OUTPUT_FILE
File to output results to
Required arguments:
-k CLIENT_ID, --client_id CLIENT_ID
CrowdStrike Falcon API key
-s CLIENT_SECRET, --client_secret CLIENT_SECRET
CrowdStrike Falcon API secret
```
### Example source code
The source code for this example can be found [here](ioc_audit.py).
---
## IOC Restore
This program will restore deleted IOCs based upon specified filter criteria.
### Running the program
In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes:
| Service Collection | Scope |
| :---- | :---- |
| IOC | __READ__, __WRITE__ |
#### Required packages
In order to run this sample, you will need to have the [`tabulate`](https://pypi.org/project/tabulate/) package installed.
### Execution syntax
This sample leverages simple command-line arguments to implement functionality.
#### Basic usage
Execute the default example. This will default to looking for IOCs that were applied globally and deleted as of today's date.
> [!NOTE]
> Times are in UTC.
```shell
python3 ioc_restore.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET
```

> This sample supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning you can execute any of the command lines shown below without providing credentials if you have the values `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` defined in your environment.
```shell
python3 ioc_restore.py
```

Change the CrowdStrike region with the `-b` argument.

```shell
python3 ioc_restore.py -b usgov1
```

Search for deleted IOCs modified by a specific user with the `-m` argument.

```shell
python3 ioc_restore.py -m username@domain.com
```

Search for deleted IOCs on a specific day using the `-dt` argument.

> [!TIP]
> This argument should be in YYYY-mm-dd format.
```shell
python3 ioc_restore.py -dt 2024-10-27
```

Search for deleted IOCs targeting a specific Host Group (by ID) using the `-hg` argument.

```shell
python3 ioc_restore.py -hg $HOST_GROUP_ID
```

Search for deleted IOCs targeting a specific Host Group (by Host Group name) using the `-g` argument.

```shell
python3 ioc_restore.py -g $HOST_GROUP_NAME
```

List all deleted IOCs discovered but take no action with the `-l` argument.

```shell
python3 ioc_restore.py -l
```

> [!TIP]
> Multiple command line parameters may be provided to refine search results.
API debugging can be enabled using the `-d` argument.

```shell
python3 ioc_restore.py -d
```

Adjust the output table format using the `-t` argument.

```shell
python3 ioc_restore.py -l -t fancy_grid
```

#### Command-line help
Command-line help is available via the `-h` argument.

```shell
usage: ioc_restore.py [-h] [-d] [-c CLIENT_ID] [-k CLIENT_SECRET] [-b BASE_URL] [-dt DATE]
[-m MODIFIED_BY] [-hg HOSTGROUP] [-g GROUPNAME] [-l] [-t TABLE_FORMAT]

Restore deleted IOCs.

_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy
`-------' `-------'
╦╔═╗╔═╗ ╦═╗┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐
║║ ║║ ╠╦╝├┤ └─┐ │ │ │├┬┘├┤
╩╚═╝╚═╝ ╩╚═└─┘└─┘ ┴ └─┘┴└─└─┘
This sample demonstrates restoring previously deleted IOCs.
~~~ API Scope Requirements ~~~
IOC Management - Read / Write
IOCs (Indicators of Compromise) - Read / Write
Creation date: 11.06.2024 - am-cs-se@CrowdStrike
Modification: 11.07.2024 - jshcodes@CrowdStrike
options:
-h, --help show this help message and exit
-d, --debug Enable API debugging
-c, --client_id CLIENT_ID
CrowdStrike API client ID
-k, --client_secret CLIENT_SECRET
CrowdStrike API client secret
-b, --base_url BASE_URL
CrowdStrike Region (US1, US2, EU1, USGOV1, USGOV2) Full URL is also supported.
-dt, --date DATE Date to target (YYYY-MM-DD)
-m, --modified_by MODIFIED_BY
User who modified the deleted IOCs
-hg, --hostgroup HOSTGROUP
ID of the Host Group associated with the IOC Not required when --groupname is
specified.
-g, --groupname GROUPNAME
Name of the Host Group associated with the IOC Not required when --hostgroup is
specified.
-l, --list List deleted IOCs but take no action
-t, --table-format TABLE_FORMAT
Tabular display format
```

### Example source code
The source code for this example can be found [here](ioc_restore.py).
35 changes: 29 additions & 6 deletions samples/ioc/create_ioc.py
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
"applied_globally": true
}
"""
import logging
import json
import os
from argparse import ArgumentParser, RawTextHelpFormatter
@@ -32,12 +33,34 @@

def consume_command_line():
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-k", "--falcon_client_id", help="Falcon API Client ID", required=True)
parser.add_argument("-s", "--falcon_client_secret", help="Falcon API Client Secret", required=True)
parser.add_argument("-m", "--method", help="SDK method to use ('service' or 'uber').", required=False, default="service")
parser.add_argument("-i", "--indicator", help="Path to the file representing the indicator (JSON format).", default="example_indicator.json", required=False)
parser.add_argument("-k", "--falcon_client_id",
help="Falcon API Client ID",
required=True)
parser.add_argument("-s", "--falcon_client_secret",
help="Falcon API Client Secret",
required=True)
parser.add_argument("-m", "--method",
help="SDK method to use ('service' or 'uber').",
required=False,
default="service")
parser.add_argument("-i", "--indicator",
help="Path to the file representing the indicator (JSON format).",
default="example_indicator.json",
required=False)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)


parsed = parser.parse_args()

return parser.parse_args()
if parsed.debug:
logging.basicConfig(level=logging.DEBUG)


return parsed


def connect_api(class_type: str = "service", creds: dict = None):
@@ -58,7 +81,7 @@ def connect_api(class_type: str = "service", creds: dict = None):
if args.method not in ["service", "uber"]:
args.method = "service"

falcon = connect_api(args.method, credentials)
falcon = connect_api(args.method, credentials, args.debug)

if not os.path.exists(args.indicator):
raise SystemExit("Unable to load indicator file.")
154 changes: 154 additions & 0 deletions samples/ioc/ioc_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄
▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▐░▌ ▐░▌ ▐░▌▐░▌
▄▄▄▄█░█▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄
▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀
▄▄▄ █ ▀
█▄▄ ▀▄▀ █▀▀ █ █ █ █▀▀ █ █▀█ █▀█ █▀▀
█▄▄ ▄▀▄ █▄▄ █▄ █▄█ ▄▄█ █ █▄█ █ █ ▄▄█
This program will output a list of IOCs and their details for either the
current CID or in each Child CID (Flight Control scenarios). This can be
used for regular audits of indicators of compromise across multiple CIDs.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2, APIError


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="List exclusions in all child CIDs (MSSP parents only)",
action="store_true",
default=False
)
parser.add_argument("-c", "--child",
help="List exclusions in a specific child CID (MSSP parents only)",
default=None
)
parser.add_argument("-o", "--output_file",
help="File to output results to",
default="iocs.txt"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


# Consume any command line arguments
cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our base authentication dictionary (parent / child)
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug,
"pythonic": True
}
local = APIHarnessV2(**auth)
# If we are in MSSP mode, retrieve our child CID details
if cmd_line.mssp:
try:
cids = local.command("getChildren", ids=local.command("queryChildren").data)
except APIError as api_erorr:
# Assume they do not have access to Flight Control
raise SystemExit("This API client does not have access to the Flight Control API scope.")
if not cids:
raise SystemExit("No child CIDs were found within this tenant.")
elif cmd_line.child:
try:
cid_name = local.command("getChildren", ids=cmd_line.child)
except APIError as api_error:
# Throw an error if they provided us an invalid CID or do not have access to Flight Control
if api_error.code == 403:
raise SystemExit("This API client does not have access to the Flight Control API scope.")
elif api_error.code == 400:
raise SystemExit("Invalid child CID provided.")
else:
raise SystemExit(api_error.message)
if cid_name:
cids = [{"name": cid_name[0]["name"], "child_cid": cmd_line.child}]
else:
raise SystemExit("The provided child CID was not found within this tenant.")
else:
# If not, we'll just run this in our current tenant
try:
cid_id = local.command("GetSensorInstallersCCIDByQuery").data[0][:-3].lower()
except APIError as api_error:
# They do not have access to the sensor downloads service collection with this key
cid_id = f"Sensor Download scope required "
cids = [{"name": "My CrowdStrike tenant",
"child_cid": cid_id
}]

# Open the output file using a context manager so it autocloses
with open(cmd_line.output_file, 'a+') as file_object:
for cid in cids:
if cmd_line.mssp or cmd_line.child:
auth["member_cid"] = cid["child_cid"]
spot = 38 - len(cid["name"])
header = f"\n\n{'*¯'*20}*\n* "+cid["name"]
header = f"{header}{' '*spot}*\n* CID: "+cid["child_cid"]+f" *\n{'*¯'*20}*\n"
print(header)
file_object.write(header)
# Connect to the API using a context manager so we autologout
with APIHarnessV2(**auth) as falcon:
# Use the combined endpoint so we don't have to make two requests
response = falcon.command("indicator_combined_v1")
if response:
for detail in response.data:
# Create our details array
details = [
"IOC: " + detail.get("value"),
"Creator: " + detail.get("created_by"),
"Created on: " + detail.get("created_on"),
"Last Modified by: " + detail.get("modified_by"),
"Last Modified on: " + detail.get("modified_on"),
"Action: " + detail.get("action")
]
print("\n".join(details))
file_object.write("\n".join(details)+"\n")
else:
print("No exclusions found")
file_object.write("No exclusions found\n")
343 changes: 343 additions & 0 deletions samples/ioc/ioc_restore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
"""Restore deleted IOCs.
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy
`-------' `-------'
╦╔═╗╔═╗ ╦═╗┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐
║║ ║║ ╠╦╝├┤ └─┐ │ │ │├┬┘├┤
╩╚═╝╚═╝ ╩╚═└─┘└─┘ ┴ └─┘┴└─└─┘
This sample demonstrates restoring previously deleted IOCs.
~~~ API Scope Requirements ~~~
IOC Management - Read / Write
IOCs (Indicators of Compromise) - Read / Write
Creation date: 11.06.2024 - am-cs-se@CrowdStrike
Modification: 11.07.2024 - jshcodes@CrowdStrike
"""
import os
import logging
from argparse import ArgumentParser, RawDescriptionHelpFormatter, Namespace, ArgumentTypeError
from datetime import datetime
from tabulate import tabulate
from falconpy import HostGroup, IOC, Result


class FalconIOCRestore:
"""Class to handle API interactions."""

def __init__(self, cmd: Namespace):
"""Construct an instance of the class."""
if cmd.debug:
logging.basicConfig(level=logging.DEBUG)
self.api: IOC = IOC(client_id=cmd.client_id,
client_secret=cmd.client_secret,
base_url=cmd.base_url,
debug=cmd.debug,
pythonic=True
)
self.hg_api: HostGroup = HostGroup(auth_object=self.api)
self.target_date: str = cmd.date
self.modified_by: str = cmd.modified_by
self.host_group: str = cmd.hostgroup
self.group_name: str = cmd.groupname

def get_host_group_id(self):
"""Return the ID of the specified host group."""
# Host Group names must be searched in LOWERCASE regardless of the actual case of the name
hg_id = self.hg_api.query_host_groups(filter=f"name:'{self.group_name.lower()}'").data
if not hg_id:
raise SystemExit("Invalid Host Group name specified.")
self.host_group = hg_id[0]

def check_ioc_exists(self, ioc_type, ioc_value):
"""Check if an IOC already exists."""
filter_query = f"type:'{ioc_type}'+value:'{ioc_value}'+deleted:false"
response: Result = self.api.indicator_search(filter=filter_query)
returned = False
if response.status_code == 200:
returned = bool(len(response.data) > 0)

return returned

def get_deleted_iocs(self): # pylint: disable=R0912
"""Get a list of deleted IOCs from specific date and modifier."""
filter_query = (f"deleted:true"
f"+modified_on:>='{self.target_date}T00:00:00Z'"
f"+modified_on:<='{self.target_date}T23:59:59Z'"
)
if self.modified_by:
filter_query = f"{filter_query}+modified_by:'{self.modified_by}'"

if self.host_group:
filter_query = f"{filter_query}+host_groups:['{self.host_group}']"
else:
filter_query = f"{filter_query}+applied_globally:true"

after = None
all_ioc_ids = []

delete_msg = f"Querying for IOCs modified on {self.target_date}"
if self.modified_by:
delete_msg = f"{delete_msg} by {self.modified_by}..."
else:
delete_msg = f"{delete_msg}..."
print(delete_msg)

while True:
response: Result = self.api.indicator_search(filter=filter_query,
limit=1000,
after=after
)

if response.status_code == 200:
ioc_ids = response.data
all_ioc_ids.extend(ioc_ids)
if response.after and ioc_ids:
after = response.after
else:
break
else:
raise SystemExit(f"API request failed: {response.errors}")

if not all_ioc_ids:
print("No deleted IOCs found for the specified criteria")
return []

all_iocs = []
chunk_size = 100
for i in range(0, len(all_ioc_ids), chunk_size):
id_list = all_ioc_ids[i:i + chunk_size]
detail_response: Result = self.api.indicator_get(ids=id_list)
if detail_response.status_code == 200:
all_iocs.extend(detail_response.data)
else:
print(f"Detail request failed for chunk {i//chunk_size + 1}: "
f"{detail_response.status_code}"
)
print(f"Detail response: {detail_response.errors}")

return all_iocs

def restore_ioc(self, ioc):
"""Restore a single IOC."""
if self.check_ioc_exists(ioc.get('type'), ioc.get('value')):
return False, "IOC already exists (skipping)"

platforms = ioc.get('platforms', ['windows'])
if isinstance(platforms, str):
platforms = [platforms]

indicator = {
"type": ioc.get('type'),
"value": ioc.get('value'),
"action": ioc.get('action', 'prevent'),
"severity": ioc.get('severity', 'high'),
"platforms": platforms,
"source": ioc.get('source', 'API Restoration'),
"description": ioc.get('description', f"Restored {ioc.get('type')} indicator")
}
if self.host_group:
indicator["host_groups"] = [self.host_group]
else:
indicator["applied_globally"] = True

request_body = {
"comment": f"Restored deleted IOC: {ioc.get('value')} ({ioc.get('type')})",
"indicators": [indicator],
}

response: Result = self.api.indicator_create(**request_body)

if response.status_code == 201:
return True, "Successfully restored"

if response.status_code == 400:
print("\nRetrying with ignore_warnings enabled...")
response = self.api.indicator_update(**request_body, ignore_warnings=True)
if response.status_code == 201:
return True, "Successfully restored with warnings ignored"

error_message = response.errors
return False, f"Failed to restore: {error_message}"


def valid_date(strdate: str) -> datetime:
"""Confirm the command line provided date is valid."""
try:
return datetime.strptime(strdate, "%Y-%m-%d")
except ValueError as bad_date:
raise ArgumentTypeError(f"not a valid date: {strdate!r}") from bad_date


def consume_arguments():
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawDescriptionHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
default=False,
required=False,
action="store_true"
)
parser.add_argument("-c", "--client_id",
help="CrowdStrike API client ID",
default=os.getenv("FALCON_CLIENT_ID"),
required=False
)
parser.add_argument("-k", "--client_secret",
help="CrowdStrike API client secret",
default=os.getenv("FALCON_CLIENT_SECRET"),
required=False
)
parser.add_argument("-b", "--base_url",
help="CrowdStrike Region (US1, US2, EU1, USGOV1, USGOV2) \n"
"Full URL is also supported.",
required=False,
default="auto"
)
parser.add_argument("-dt", "--date",
help="Date to target (YYYY-MM-DD)",
default=datetime.now().strftime("%Y-%m-%d"),
required=False,
type=valid_date
)
parser.add_argument("-m", "--modified_by",
help="User who modified the deleted IOCs",
required=False,
default=None
)
parser.add_argument("-hg", "--hostgroup",
help="ID of the Host Group associated with the IOC\n"
"Not required when --groupname is specified.",
required=False,
default=None
)
parser.add_argument("-g", "--groupname",
help="Name of the Host Group associated with the IOC\n"
"Not required when --hostgroup is specified.",
required=False,
default=None
)
parser.add_argument("-l", "--list",
help="List deleted IOCs but take no action",
default=False,
required=False,
action="store_true"
)
parser.add_argument("-t", "--table-format",
dest="table_format",
help="Tabular display format",
required=False,
default="simple"
)

parsed = parser.parse_args()
parsed.date = str(parsed.date).split(" ", maxsplit=1)[0]

return parsed


def main(): # pylint: disable=R0912,R0915
"""Execute the main routine."""
cmd_line = consume_arguments()
falcon = FalconIOCRestore(cmd_line)
if falcon.group_name:
falcon.get_host_group_id()
deleted_iocs = falcon.get_deleted_iocs()

if deleted_iocs and not cmd_line.list:
print(f"\nFound {len(deleted_iocs)} deleted IOCs for {cmd_line.date} "
f"modified by {cmd_line.modified_by}"
)
print("-" * 100)

successful_restores = 0
failed_restores = 0
skipped_restores = 0
failed_details = []

for ioc in deleted_iocs:
print("\nProcessing IOC:")
print(f"Type: {ioc.get('type')}")
print(f"Value: {ioc.get('value')}")
print(f"Action: {ioc.get('action', 'prevent')}")
print(f"Severity: {ioc.get('severity', 'high')}")
print(f"Platforms: {', '.join(ioc.get('platforms', ['windows']))}")
print(f"Modified On: {ioc.get('modified_on')}")
print(f"Modified By: {ioc.get('modified_by')}")

success, message = falcon.restore_ioc(ioc)

if success:
successful_restores += 1
print("✓ Successfully restored")
elif "already exists" in message:
skipped_restores += 1
print(f"⚠ {message}")
else:
failed_restores += 1
failed_details.append(f"{ioc.get('value')} - {message}")
print(f"✗ Failed to restore: {message}")

print("-" * 100)

print("\nRestoration Summary:")
print(f"Total IOCs processed: {len(deleted_iocs)}")
print(f"Successfully restored: {successful_restores}")
print(f"Skipped (already exist): {skipped_restores}")
print(f"Failed to restore: {failed_restores}")

if failed_details:
print("\nFailed IOCs details:")
for detail in failed_details:
print(f"- {detail}")
elif deleted_iocs and cmd_line.list:
display_keys = {"id": "ID",
"type": "Type",
"value": "Value",
"action": "Action",
"severity": "Severity",
"target": "Target",
"modification": "Modification"
}
pop_keys = ["source",
"mobile_action",
"description",
"expired",
"platforms",
"host_groups",
"deleted",
"applied_globally",
"from_parent",
"created_on",
"created_by",
"modified_on",
"modified_by"
]
show_iocs = []
for ioc in deleted_iocs:
if ioc.get("applied_globally"):
ioc["target"] = "Global"
else:
ioc["target"] = falcon.hg_api.get_host_groups(
ids=falcon.host_group
).data[0].get("name")
ioc["modification"] = f"{ioc['modified_by']}\n{ioc['modified_on']}"
if ioc["expired"]:
ioc["id"] = f"{ioc['id']}\nEXPIRED"
for key in pop_keys:
ioc.pop(key)
show_iocs.append(ioc)
print(tabulate(show_iocs, headers=display_keys, tablefmt=cmd_line.table_format))
else:
print(f"No deleted IOCs found for the specified criteria on {cmd_line.date}")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions samples/ioc/ioc_restore_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
crowdstrike-falconpy
tabulate
3 changes: 2 additions & 1 deletion samples/malquery/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# MalQuery examples
190 changes: 61 additions & 129 deletions samples/malquery/malqueryinator.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
"""
MalQueryinator - MalQuery sample download utility.
"""MalQueryinator - MalQuery sample download utility.
___ ___ __ _______
| Y .---.-| | _ .--.--.-----.----.--.--.
|. | _ | |. | | | | -__| _| | |
|. \_/ |___._|__|. | |_____|_____|__| |___ |
|: | | |: 1 | |_____|
|::.|:. | |::.. |
`--- ---' `----|:.| FalconPy v1.3.0+
`--'
Searches MalQuery (fuzzy) for a particular string,
downloading a specified number of examples if found.
09.02.21 - jlangdev@CrowdStrike, jshcodes@CrowdStrike
02.09.23 - jshcodes@Crowdstrike
"""
# ___ ___ __ _______
# | Y .---.-| | _ .--.--.-----.----.--.--.
# |. | _ | |. | | | | -__| _| | |
# |. \_/ |___._|__|. | |_____|_____|__| |___ |
# |: | | |: 1 | |_____|
# |::.|:. | |::.. |
# `--- ---' `----|:.| FalconPy v0.7.0+
# `--'

import argparse
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter
try:
from falconpy import APIHarness
from falconpy import APIHarnessV2, version
except ImportError as no_falconpy:
raise SystemExit(
"CrowdStrike FalconPy must be installed in order to use this application.\n"
"CrowdStrike FalconPy 1.3 or greater must be installed in order to use this application.\n"
"Please execute `python3 -m pip install crowdstrike-falconpy` and try again."
) from no_falconpy


def malware_search(type_, value, limit):
"""
Performs a fuzzy MalQuery search based
upon the type and value provided.
"""
"""Perform a fuzzy MalQuery search based upon the type and value provided."""
stub = ""
if int(limit) > 1:
stub = "s"
@@ -51,10 +50,9 @@ def malware_search(type_, value, limit):


def id_search(malware):
"""
Requests the download for the ID returned from
the fuzzy malware_search. Displays the details
for the malware sample that is to be retrieved.
"""Request the download for the ID returned from the fuzzy malware_search.
Displays the details for the malware sample that is to be retrieved.
"""
id_to_retrieve = []
for found in malware:
@@ -77,10 +75,7 @@ def id_search(malware):


def get_malquery_request(search_request_id):
"""
Checks the status of our download request,
waiting until the status is set to "done".
"""
"""Check the status of our download request, waiting until the status is set to "done"."""
print("Getting malquery request")
running = True
while running:
@@ -94,10 +89,7 @@ def get_malquery_request(search_request_id):


def get_sample(search_request_id: str, save_file: str):
"""
Retrieves the sample from MalQuery,
downloading to the file specified.
"""
"""Retrieve the sample from MalQuery, downloading to the file specified."""
print(
f"Downloading samples {search_request_id} to ./{save_file}"
)
@@ -114,64 +106,52 @@ def get_sample(search_request_id: str, save_file: str):
saving.write(archive_result)


def connect_api(key: str, secret: str):
"""
Connects and returns an instance of the Uber class.
"""
return APIHarness(client_id=key, client_secret=secret)
def connect_api(key: str, secret: str, debug: bool):
"""Connects and returns an instance of the Uber class."""
if debug:
logging.basicConfig(level=logging.DEBUG)
return APIHarnessV2(client_id=key, client_secret=secret, debug=debug)


def parse_command_line():
"""
Parses the passed command line and
returns the created args object.
"""
parser = argparse.ArgumentParser(
description="Malquerinator"
)
"""Parses the passed command line and returns the created args object."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
# Type defaults to "ascii" when not provided
parser.add_argument(
'-t', '--type',
help="Type of pattern for the malware query: ascii, hex, or wide",
required=False
)

parser.add_argument(
'-v', '--value',
help="Value for malware query of type determined by --t/--type arg",
required=True
)

parser.add_argument(
'-f', '--file',
help="Name of file to write to",
required=True
)

parser.add_argument(
'-e', '--examples',
help="Number of examples to download",
required=False
)

parser.add_argument(
'-k', '--key',
help='Falcon API Client ID',
required=True
)
parser.add_argument(
'-s', '--secret',
help='Falcon API Client secret',
required=True
)
parser.add_argument("-t", "--type",
help="Type of pattern for the malware query: ascii, hex, or wide"
)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-v", "--value",
help="Value for malware query of type determined by --t/--type arg",
required=True
)
parser.add_argument("-f", "--file", help="Name of file to write to", required=True)
parser.add_argument("-e", "--examples", help="Number of examples to download")

parser.add_argument("-k", "--key",
help="Falcon API Client ID",
default=os.getenv("FALCON_CLIENT_ID")
)
parser.add_argument("-s", "--secret",
help="Falcon API Client secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)

parsed = parser.parse_args()
if not parsed.key or not parsed.secret:
parser.error(
"You must provide valid API credentials ('-k' and '-s') in order to use this program."
)

return parser.parse_args()
return parsed


def main():
"""
Main routine
"""
"""Execute main routine."""
malware = malware_search(QUERY_TYPE, query_value, EXAMPLES)
search_request_id = id_search(malware)
get_malquery_request(search_request_id)
@@ -195,55 +175,7 @@ def main():

query_value = args.value
file = args.file
falcon = connect_api(key=args.key, secret=args.secret)
falcon = connect_api(key=args.key, secret=args.secret, debug=args.debug)

if __name__ == "__main__":
main()


#
# WNNW
# WKdcclx0XN
# N0xl,',;cxX
# W0l,..'cON
# WWW Nx:,..,dX
# NKOxdolllodk0NWNXd,..,xW
# NOoc;''.......',:lxkd;..'lX
# WOc:;;;;;;,,''......'''...:kOkkkkk0N
# WNNNXXKOxoc;'............'''',:okKW
# NOl;,'..................',:clo0W
# Xo,',;:lodddollllc;'....'''...'dW
# W0dxOKXNWWWNK000000d,....'cdl,.,xW
# WW WX0OkdodkOkc'....,xXOc;k
# W0xd0N WN00Oo:,...,do'.....:KWX0X
# WOlcclkN WK0000OOOkoc,;ol;;;;,;OW
# WNNNNXXNWX000000O00000Odk0O0XXKKN
# WNKkOXNXXKOxddOKXKK0O0000000X This Inator has been Doof-approved!
# W0c:lkNWWWXd'';dXWNWNXOkO000KN
# N0xxOXWWWNKkl:oKNWWWWWX0OO00XW /
# WXXWWWWWWNKKNNNWWWWWWWNX0O00N
# WNNNWWWNXOOXWWWWWWWWWKOO00KN
# WXOkkkOOOO0XNWWWWNX0kkO00KWWNNXXXNW
# WWNNNNNXXXKkxxkO000OOOOOOOxxxO000O0K00O0000X
# WNXKK00000000000000000000OkxxxkO00000OO00OO0000X
# WNKK000000000000000000000000000000000000000OOO000XW
# WXK000000000KKKK0OO000000000000000000000000KKKKXXNW
# NKO000KKXXXNWWWWK00000000000000000000000000KW
# WXXNNWW WWNKO00000000000000000000000000N
# NK00OOOOOOOOOOOOOOOOO0000000000KN
# WK00000000000OO0000000000000000KN
# NK000000000000000000000000000000XW
# NK00000000000000000000000000000000XNW WXKNW
# NK00000000000000000000000000000000000KKx;.'dXNWW
# WK00000000000000000000000000000000000000k: ,0WNNNW
# X000000000000000000000000000000000000000Oc .lXWWNXNWW
# WX000000000000000KKKKXXNNNNNNNNNNNNNXX0d:,. .lX WWNXNNW
# NK0000000KKXXNNWWW WXl. .xW WWWNNNNW
# WNXXXNNWW NNKc ;KWW WNNNNWW
# WNNWx. .oN W WWNNNWWW
# NNW 0' 'OW W WNNWWWNW
# WNN X: cXW WWNNWW WWNW
# WNW No .OWNNNWWWW WWNNW
# WXNNWWk. .oXXNWWWW WWW WNNW
# NXXNW0, :KWNNNNNW WWWWWNNW
# WXXNW X: ,0 WWWNNWW W WNN
117 changes: 117 additions & 0 deletions samples/ml_exclusions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png#gh-light-mode-only)
![CrowdStrike FalconPy](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo-red.png#gh-dark-mode-only)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# ML Exclusions samples
The examples within this folder focus on leveraging CrowdStrike Falcon ML Exclusions collection.

- [MLE Audit](#mle-audit)

## MLE Audit
This program will output a list of ML exclusions and their details for either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of ML exclusions across multiple CIDs.

### Running the program
In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes:

| Service Collection | Scope |
| :---- | :---- |
| ML Exclusions | __READ__ |
| Flight Control | __READ__ |
| Sensor Download | __READ__ |

> [!NOTE]
> This program can be executed using an API key that is not scoped for the Flight Control (MSSP) and Sensor Download service collections, but will be unable to lookup the current CID (Sensor Download) or access child CIDs (Flight Control).
### Execution syntax
This sample leverages simple command-line arguments to implement functionality.

#### Basic usage
Execute the default example. This will output results to a CSV file named `ml_exclusions.txt`.

```shell
python3 mle_audit.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET
```

> This sample supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning you can execute any of the command lines shown below without providing credentials if you have the values `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` defined in your environment.
```shell
python3 mle_audit.py
```

Change the output destination with the `-o` argument.

```shell
python3 mle_audit.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -o new_ml_exclusions.txt
```

Enable MSSP mode and audit all Flight Control children with the `-m` argument.

```shell
python3 mle_audit.py -k $FALCON_CLIENT_ID_PARENT -s $FALCON_CLIENT_SECRET_PARENT -m
```

Enable MSSP mode and audit a specific Flight Control child with the `-c` argument.

```shell
python3 mle_audit.py -k $FALCON_CLIENT_ID_PARENT -s $FALCON_CLIENT_SECRET_PARENT -c CHILD_CID
```

> API debugging can be enabled using the `-d` argument.
```shell
python3 mle_audit.py -d
```

#### Command-line help
Command-line help is available via the `-h` argument.

```shell
usage: mle_audit.py [-h] [-d] [-m] [-c CHILD] [-o OUTPUT_FILE] [-k CLIENT_ID] [-s CLIENT_SECRET]

_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
______ _______ ____
| \/ \ | |
/ /\ \ | |
/ /\ / /\ || |
/ /\ \_/ / / /|| | ____
| | \|_|/ / / || | | |
| | | | || | | |
|\____\ |____| /|____|/____/|
| | | | | / | | ||
\|____| |____|/ |____|_____|/
▄▄▄ █ ▀
█▄▄ ▀▄▀ █▀▀ █ █ █ █▀▀ █ █▀█ █▀█ █▀▀
█▄▄ ▄▀▄ █▄▄ █▄ █▄█ ▄▄█ █ █▄█ █ █ ▄▄█
This program will output a list of machine learning exclusions and their
details for either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of IOA exclusions across multiple CIDs.
Developed by @Don-Swanson-Adobe
optional arguments:
-h, --help show this help message and exit
-d, --debug Enable API debugging
-m, --mssp List exclusions in all child CIDs (MSSP parents only)
-c CHILD, --child CHILD
List exclusions in a specific child CID (MSSP parents only)
-o OUTPUT_FILE, --output_file OUTPUT_FILE
File to output results to
Required arguments:
-k CLIENT_ID, --client_id CLIENT_ID
CrowdStrike Falcon API key
-s CLIENT_SECRET, --client_secret CLIENT_SECRET
CrowdStrike Falcon API secret
```
### Example source code
The source code for this example can be found [here](mle_audit.py).
153 changes: 153 additions & 0 deletions samples/ml_exclusions/mle_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""
_______ __ _______ __ __ __
| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----.
|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__|
|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____|
|: 1 | |: 1 |
|::.. . | |::.. . | FalconPy
`-------' `-------'
______ _______ ____
| \/ \ | |
/ /\ \ | |
/ /\ / /\ || |
/ /\ \_/ / / /|| | ____
| | \|_|/ / / || | | |
| | | | || | | |
|\____\ |____| /|____|/____/|
| | | | | / | | ||
\|____| |____|/ |____|_____|/
▄▄▄ █ ▀
█▄▄ ▀▄▀ █▀▀ █ █ █ █▀▀ █ █▀█ █▀█ █▀▀
█▄▄ ▄▀▄ █▄▄ █▄ █▄█ ▄▄█ █ █▄█ █ █ ▄▄█
This program will output a list of machine learning exclusions and their
details for either the current CID or in each Child CID (Flight Control scenarios).
This can be used for regular audits of ML exclusions across multiple CIDs.
Developed by @Don-Swanson-Adobe
"""
import os
import logging
from argparse import ArgumentParser, RawTextHelpFormatter, Namespace
from falconpy import APIHarnessV2, APIError


def consume_arguments() -> Namespace:
"""Consume any provided command line arguments."""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument("-d", "--debug",
help="Enable API debugging",
action="store_true",
default=False
)
parser.add_argument("-m", "--mssp",
help="List exclusions in all child CIDs (MSSP parents only)",
action="store_true",
default=False
)
parser.add_argument("-c", "--child",
help="List exclusions in a specific child CID (MSSP parents only)",
default=None
)
parser.add_argument("-o", "--output_file",
help="File to output results to",
default="ml_exclusions.txt"
)
req = parser.add_argument_group("Required arguments")
req.add_argument("-k", "--client_id",
help="CrowdStrike Falcon API key",
default=os.getenv("FALCON_CLIENT_ID")
)
req.add_argument("-s", "--client_secret",
help="CrowdStrike Falcon API secret",
default=os.getenv("FALCON_CLIENT_SECRET")
)
parsed = parser.parse_args()
if not parsed.client_id or not parsed.client_secret:
parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.")

return parsed


# Consume any command line arguments
cmd_line = consume_arguments()

# Activate debugging if requested
if cmd_line.debug:
logging.basicConfig(level=logging.DEBUG)

# Create our base authentication dictionary (parent / child)
auth = {
"client_id": cmd_line.client_id,
"client_secret": cmd_line.client_secret,
"debug": cmd_line.debug,
"pythonic": True
}
local = APIHarnessV2(**auth)
# If we are in MSSP mode, retrieve our child CID details
if cmd_line.mssp:
try:
cids = local.command("getChildren", ids=local.command("queryChildren").data)
except APIError as api_erorr:
# Assume they do not have access to Flight Control
raise SystemExit("This API client does not have access to the Flight Control API scope.")
if not cids:
raise SystemExit("No child CIDs were found within this tenant.")
elif cmd_line.child:
try:
cid_name = local.command("getChildren", ids=cmd_line.child)
except APIError as api_error:
# Throw an error if they provided us an invalid CID or do not have access to Flight Control
if api_error.code == 403:
raise SystemExit("This API client does not have access to the Flight Control API scope.")
elif api_error.code == 400:
raise SystemExit("Invalid child CID provided.")
else:
raise SystemExit(api_error.message)
if cid_name:
cids = [{"name": cid_name[0]["name"], "child_cid": cmd_line.child}]
else:
raise SystemExit("The provided child CID was not found within this tenant.")
else:
# If not, we'll just run this in our current tenant
try:
cid_id = local.command("GetSensorInstallersCCIDByQuery").data[0][:-3].lower()
except APIError as api_error:
# They do not have access to the sensor downloads service collection with this key
cid_id = f"Sensor Download scope required "
cids = [{"name": "My CrowdStrike tenant",
"child_cid": cid_id
}]

# Open the file using a context manager so it auto-closes
with open(cmd_line.output_file, 'a+') as file_object:
for cid in cids:
if cmd_line.mssp or cmd_line.child:
auth["member_cid"] = cid["child_cid"]
spot = 38 - len(cid["name"])
header = f"\n\n{'*¯'*20}*\n* "+cid["name"]
header = f"{header}{' '*spot}*\n* CID: "+cid["child_cid"]+f" *\n{'*¯'*20}*\n"
print(header)
file_object.write(header)
# Open the API using a context manager so we automatically log out
with APIHarnessV2(**auth) as falcon:
# Query for the list of MLEs in the CID, pull the details, and log / display results
response = falcon.command("queryMLExclusionsV1")
if response:
mleresponse = falcon.command("getMLExclusionsV1", ids=response.data)
if mleresponse:
for detail in mleresponse.data:
details = [
"MLE: " + detail["value"],
"Creator: " + detail["created_by"],
"Created on: " + detail["created_on"],
"Last Modified by: " + detail["modified_by"],
"Last Modified on: " + detail["last_modified"]
]
print("\n".join(details))
file_object.write("\n".join(details)+"\n")
else:
print("No exclusions found")
file_object.write("No exclusions found\n")
Loading