mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
240 lines
7.6 KiB
Markdown
240 lines
7.6 KiB
Markdown
Using `curl` and `jq` to interact with the fleet API.
|
|
|
|
First, create a `env` file with the following contents:
|
|
|
|
```
|
|
export SERVER_URL=https://localhost:8080 # your fleet server url and port
|
|
export CURL_FLAGS='-k -s' # set insecure flag
|
|
export TOKEN=eyJhbGciOi... # your api token
|
|
```
|
|
|
|
Next set the `FLEET_ENV_PATH` to point to the `env` file. This will let the scripts in the `fleet/` folder source the env file.
|
|
|
|
# Examples
|
|
|
|
```
|
|
export FLEET_ENV_PATH=./path/to/env/file/fleet_env
|
|
|
|
# get my user info
|
|
./tools/api/fleet/me
|
|
{
|
|
"user": {
|
|
"created_at": "2018-04-10T02:07:46Z",
|
|
"updated_at": "2018-04-10T02:07:46Z",
|
|
"id": 1,
|
|
"name": "admin",
|
|
"email": "admin@acme.co",
|
|
"admin": true,
|
|
"enabled": true,
|
|
"force_password_reset": false,
|
|
"gravatar_url": "",
|
|
"sso_enabled": false
|
|
}
|
|
}
|
|
|
|
# list queries
|
|
./tools/api/fleet/queries/list
|
|
{
|
|
"queries": []
|
|
}
|
|
|
|
# use jq to filter a specific query and get the id
|
|
./tools/api/fleet/queries/list | jq '.queries[]|select(.name == "osquery_info")|.id'
|
|
2
|
|
|
|
# create a query
|
|
./tools/api/fleet/queries/create 'system_info' 'SELECT * FROM system_info;'
|
|
{
|
|
"query": {
|
|
"created_at": "0001-01-01T00:00:00Z",
|
|
"updated_at": "0001-01-01T00:00:00Z",
|
|
"id": 4,
|
|
"name": "system_info",
|
|
"description": "",
|
|
"query": "SELECT * FROM system_info;",
|
|
"saved": true,
|
|
"author_id": 1,
|
|
"author_name": "admin",
|
|
"packs": []
|
|
}
|
|
}
|
|
|
|
# add query with id=4 to pack with id=2
|
|
./tools/api/fleet/schedule/add_query_to_pack 2 4
|
|
|
|
# get scheduled queries in a pack
|
|
./tools/api/fleet/packs/scheduled 2 | jq '.scheduled[]|{"name": .name, "schedule_id": .id, "query_id": .query_id}'
|
|
|
|
# run a live queries on hosts (queries with id=1 and id=2 on hosts with id=3 and id=4)
|
|
./tools/api/fleet/queries/run "[1,2]" "[3,4]"
|
|
```
|
|
|
|
> The following examples are made obsolete with the vuln endpoint https://fleetdm.com/docs/rest-api/rest-api#vulnerabilities
|
|
|
|
|
|
Bash Script - Pulls all hosts based on software _name_ for your Fleet instance, uses jq. Helps if wanting to track down a particular software and see what hosts might be affected.
|
|
|
|
`./name.sh api_token software_title_id base_url`
|
|
|
|
```
|
|
#!/bin/bash
|
|
|
|
# Check if we have the correct number of arguments
|
|
if [ "$#" -ne 3 ]; then
|
|
echo "Usage: $0 <api_token> <software_title_id> <base_url>"
|
|
exit 1
|
|
fi
|
|
|
|
# Read arguments
|
|
API_TOKEN="$1"
|
|
SOFTWARE_TITLE_ID="$2"
|
|
BASE_URL="$3"
|
|
|
|
# Get the version IDs for the software title
|
|
VERSION_IDS=$(curl -s "${BASE_URL}/software/titles/${SOFTWARE_TITLE_ID}" \
|
|
-H 'accept: application/json, text/plain, */*' \
|
|
-H "authorization: Bearer ${API_TOKEN}" \
|
|
--compressed | jq '.software_title.versions[].id')
|
|
|
|
# Define a jq filter for deduplicating hosts by id
|
|
jq_filter='[.[] | {id: .id, hostname: .hostname}] | unique_by(.id)'
|
|
|
|
# Make a temporary file to hold all host entries
|
|
tmpfile=$(mktemp)
|
|
|
|
# Loop through each version ID and get the hosts
|
|
for version_id in $VERSION_IDS; do
|
|
# Fetch hosts for the current version ID
|
|
curl -s "${BASE_URL}/hosts?software_version_id=${version_id}" \
|
|
-H 'accept: application/json, text/plain, */*' \
|
|
-H "authorization: Bearer ${API_TOKEN}" \
|
|
--compressed | jq '.hosts[]' >> "$tmpfile"
|
|
done
|
|
|
|
# Deduplicate hosts by id and convert to a JSON array
|
|
jq -s "$jq_filter" "$tmpfile"
|
|
|
|
# Remove the temporary file
|
|
rm "$tmpfile"
|
|
```
|
|
|
|
Some quick Python to pull all Vuln software per host
|
|
Might be better to do this _backwards_ by host instead of by the software. Attempting to use parallel threading to make it run faster, only helps a little.
|
|
can adjust `structure` to display what info you want.
|
|
|
|
|
|
```
|
|
import requests
|
|
import time
|
|
import json
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
# Define the base URL for the API
|
|
BASE_URL = "https://dogfood.fleetdm.com/api/latest" #change to your base url
|
|
|
|
# The headers for the HTTP requests, including the Authorization Bearer token
|
|
HEADERS = {
|
|
'Authorization': 'Bearer TOKEN', # Add your API token
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
# Initialize counters for API calls and hits
|
|
api_calls_counter = 0
|
|
hits_counter = 0
|
|
|
|
# Initialize a cache to store hosts for software versions
|
|
version_hosts_cache = {}
|
|
|
|
|
|
# Function to get all the software titles with vulnerabilities
|
|
def get_all_vulnerable_software_titles():
|
|
global api_calls_counter
|
|
endpoint = (f"{BASE_URL}/fleet/software/titles?scope=software-titles&page=0&per_page=1000&order_direction=desc&order_key=hosts_count&vulnerable=true") #note that this is set to 1k to try and capture "all" but might need to adjust
|
|
response = requests.get(endpoint, headers=HEADERS)
|
|
api_calls_counter += 1
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
return data.get('software_titles', [])
|
|
else:
|
|
raise Exception(f"Error fetching software titles: {response.text}")
|
|
|
|
|
|
# Function to get the hosts for a software version with caching
|
|
def get_hosts_for_software_version(version_id):
|
|
global api_calls_counter
|
|
global hits_counter
|
|
|
|
# Check the cache first
|
|
if version_id in version_hosts_cache:
|
|
return version_hosts_cache[version_id]
|
|
|
|
# If not cached, make the request
|
|
endpoint = f"{BASE_URL}/fleet/hosts?software_version_id={version_id}"
|
|
response = requests.get(endpoint, headers=HEADERS)
|
|
api_calls_counter += 1
|
|
|
|
if response.status_code == 200:
|
|
hosts = response.json().get('hosts', [])
|
|
hits_counter += len(hosts)
|
|
# Cache the result
|
|
version_hosts_cache[version_id] = hosts
|
|
return hosts
|
|
else:
|
|
raise Exception(f"Error fetching hosts for software version {version_id}: {response.text}")
|
|
|
|
|
|
# Function to fetch hosts for all vulnerable software versions in parallel using threading
|
|
def fetch_hosts_in_parallel(software_versions):
|
|
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
future_to_version_id = {executor.submit(get_hosts_for_software_version, v['id']): v['id'] for v in
|
|
software_versions}
|
|
for future in future_to_version_id:
|
|
future.result() # We wait for each call to complete here. The results are stored in the cache.
|
|
|
|
|
|
# Main function to build the desired structure
|
|
def build_structure():
|
|
global api_calls_counter
|
|
global hits_counter
|
|
|
|
api_calls_counter = 0
|
|
hits_counter = 0
|
|
|
|
software_titles = get_all_vulnerable_software_titles()
|
|
vulnerable_versions = [version for software in software_titles for version in software.get('versions', []) if
|
|
version.get('vulnerabilities')]
|
|
fetch_hosts_in_parallel(vulnerable_versions) # Fetch all hosts in parallel
|
|
|
|
structure = {}
|
|
for software in software_titles:
|
|
for version in software.get('versions', []):
|
|
if version.get('vulnerabilities'):
|
|
version_id = version['id']
|
|
hosts = version_hosts_cache.get(version_id, [])
|
|
for host in hosts:
|
|
host_id = host['id']
|
|
if host_id not in structure:
|
|
structure[host_id] = {
|
|
"hostname": host['hostname'],
|
|
"software": []
|
|
}
|
|
structure[host_id]['software'].append({
|
|
"version_id": str(version_id),
|
|
"software_id": str(software['id']),
|
|
"name": software['name']
|
|
})
|
|
|
|
|
|
return structure
|
|
|
|
|
|
# Run the main function and print results
|
|
if __name__ == "__main__":
|
|
start_time = time.time()
|
|
final_structure = build_structure()
|
|
print(json.dumps(final_structure, indent=2))
|
|
#print(f"Total time taken: {time.time() - start_time:.2f} seconds")
|
|
#print(f"API Calls: {api_calls_counter}")
|
|
#print(f"total number of software vulns: {hits_counter}")
|
|
```
|