Skip to content

Instantly share code, notes, and snippets.

@yannhowe
Created November 19, 2025 16:03
Show Gist options
  • Select an option

  • Save yannhowe/8971792f6787d7e6941ae4ea6c398c76 to your computer and use it in GitHub Desktop.

Select an option

Save yannhowe/8971792f6787d7e6941ae4ea6c398c76 to your computer and use it in GitHub Desktop.
Retrieves container image details and software packages (SBOM) from CrowdStrike Falcon Cloud Security APIs in structured JSON format.
#!/bin/bash
# CrowdStrike Container Image & Package Scanner (JSON Output)
#
# This script retrieves container image details and software packages (SBOM)
# from CrowdStrike Falcon Cloud Security APIs in JSON format.
#
# Author: CrowdStrike
# Version: 1.0
# License: MIT
set -e
# Configuration
FALCON_CLOUD=${FALCON_CLOUD:-"us-1"}
BASE_URL="https://api.crowdstrike.com"
# Function to show comprehensive help
show_help() {
cat << 'EOF'
================================================================================
CrowdStrike Container Image & Package Scanner (JSON Output)
================================================================================
DESCRIPTION:
Retrieves container image details and software packages (SBOM) from
CrowdStrike Falcon Cloud Security APIs in structured JSON format.
PREREQUISITES:
1. CrowdStrike API credentials with 'falcon-container-image:read' scope
2. jq command-line JSON processor
3. curl for API requests
SETUP:
export FALCON_CLIENT_ID="your-client-id"
export FALCON_CLIENT_SECRET="your-client-secret"
export FALCON_CLOUD="us-1" # Optional: us-1, us-2, eu-1, us-gov-1
USAGE:
./get-image-packages-json.sh [OPTIONS]
OPTIONS:
--repository REPO Filter by repository name
--registry REG Filter by registry
--tag TAG Filter by tag
--digest DIGEST Filter by image digest
--limit NUM Limit images (default: 3, max: 100)
--package-limit NUM Limit packages per image (default: 1000)
--format FORMAT Output format (default: combined)
-h, --help Show this help
OUTPUT FORMATS:
combined - Images with their packages (default)
images-only - Only image metadata
packages-only - Only packages for first image found
EXAMPLES:
# Get 2 images with packages in combined format
./get-image-packages-json.sh --limit 2
# Get nginx images with packages
./get-image-packages-json.sh --repository nginx --limit 1
# Get only image metadata from Docker Hub
./get-image-packages-json.sh --registry docker.io --format images-only
# Get packages for specific image
./get-image-packages-json.sh --digest sha256:abc123... --format packages-only
# Filter by multiple criteria
./get-image-packages-json.sh --registry docker.io --tag latest --limit 5
OUTPUT STRUCTURE (combined format):
{
"images": [
{
"registry": "docker.io",
"repository": "nginx",
"tag": "latest",
"digest": "sha256:...",
"base_os": "Debian GNU",
"config": {"architecture": "amd64"},
"packages": [
{
"package_name_version": "nginx-1.21.6-1",
"type": "apt",
"license": "BSD-2-Clause",
"vulnerability_count": 0,
"vulnerabilities": []
}
]
}
],
"total_images": 1,
"generated_at": "2025-11-19T15:54:48Z"
}
FILTERING:
Available filter fields:
- registry: Container registry (docker.io, gcr.io, etc.)
- repository: Repository name (nginx, alpine, etc.)
- tag: Image tag (latest, stable, v1.0, etc.)
- digest: SHA256 digest
- Multiple filters are combined with AND logic
API ENDPOINTS USED:
- POST /oauth2/token (authentication)
- GET /container-security/combined/images/detail/v1 (image details)
- GET /container-security/combined/packages/v1 (package inventory)
TROUBLESHOOTING:
1. "Failed to get access token":
- Verify FALCON_CLIENT_ID and FALCON_CLIENT_SECRET
- Check API client has 'falcon-container-image:read' scope
2. "jq: command not found":
- Install jq: apt-get install jq (Ubuntu) or brew install jq (macOS)
3. Empty results:
- Check filters aren't too restrictive
- Verify container images exist in your environment
4. Rate limiting:
- Reduce --limit and --package-limit values
- Add delays between requests if running multiple times
SUPPORT:
CrowdStrike Developer Portal: https://falcon.crowdstrike.com/documentation
Container Security APIs: https://falcon.crowdstrike.com/documentation/page/container-security-apis
================================================================================
EOF
}
# Function to show usage summary
show_usage() {
cat << 'EOF'
CrowdStrike Container Image & Package Scanner (JSON Output)
Usage: ./get-image-packages-json.sh [OPTIONS]
Common Examples:
./get-image-packages-json.sh --limit 2
./get-image-packages-json.sh --repository nginx --limit 1
./get-image-packages-json.sh --registry docker.io --format images-only
Setup Required:
export FALCON_CLIENT_ID="your-client-id"
export FALCON_CLIENT_SECRET="your-client-secret"
Use --help for detailed documentation.
EOF
}
# Check if no arguments provided
if [[ $# -eq 0 ]]; then
show_usage
exit 0
fi
# Check required environment variables
if [[ -z "$FALCON_CLIENT_ID" || -z "$FALCON_CLIENT_SECRET" ]]; then
echo "❌ ERROR: Missing CrowdStrike API credentials" >&2
echo "" >&2
echo "Required environment variables:" >&2
echo " export FALCON_CLIENT_ID=\"your-client-id\"" >&2
echo " export FALCON_CLIENT_SECRET=\"your-client-secret\"" >&2
echo "" >&2
echo "Optional:" >&2
echo " export FALCON_CLOUD=\"us-1\" # us-1, us-2, eu-1, us-gov-1" >&2
echo "" >&2
echo "Get API credentials from: https://falcon.crowdstrike.com/api-clients-and-keys" >&2
echo "Required scope: falcon-container-image:read" >&2
echo "" >&2
echo "Use --help for complete documentation." >&2
exit 1
fi
# Check for jq dependency
if ! command -v jq &> /dev/null; then
echo "❌ ERROR: jq is required but not installed" >&2
echo "" >&2
echo "Install jq:" >&2
echo " Ubuntu/Debian: sudo apt-get install jq" >&2
echo " macOS: brew install jq" >&2
echo " RHEL/CentOS: sudo yum install jq" >&2
exit 1
fi
# Function to get OAuth2 token
get_token() {
local response
response=$(curl -s -X POST "$BASE_URL/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=$FALCON_CLIENT_ID&client_secret=$FALCON_CLIENT_SECRET")
local token
token=$(echo "$response" | jq -r '.access_token')
if [[ "$token" == "null" ]]; then
echo "Failed to get access token:" >&2
echo "$response" | jq '.' >&2
exit 1
fi
echo "$token"
}
# Function to get image details
get_image_details() {
local filter="$1"
local limit="${2:-3}"
local params="limit=$limit&with_config=true"
if [[ -n "$filter" ]]; then
params="$params&filter=$filter"
fi
curl -s -X GET "$BASE_URL/container-security/combined/images/detail/v1?$params" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json"
}
# Function to get packages for specific image
get_image_packages() {
local image_digest="$1"
local limit="${2:-1000}"
local filter="image_digest:'$image_digest'"
local params="limit=$limit&filter=$filter"
curl -s -X GET "$BASE_URL/container-security/combined/packages/v1?$params" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json"
}
# Parse command line arguments
REPOSITORY=""
REGISTRY=""
TAG=""
IMAGE_DIGEST=""
LIMIT=3
PACKAGE_LIMIT=1000
OUTPUT_FORMAT="combined" # combined, images-only, packages-only
while [[ $# -gt 0 ]]; do
case $1 in
--repository)
REPOSITORY="$2"
shift 2
;;
--registry)
REGISTRY="$2"
shift 2
;;
--tag)
TAG="$2"
shift 2
;;
--digest)
IMAGE_DIGEST="$2"
shift 2
;;
--limit)
LIMIT="$2"
shift 2
;;
--package-limit)
PACKAGE_LIMIT="$2"
shift 2
;;
--format)
OUTPUT_FORMAT="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
# Get access token (suppress output to stderr)
ACCESS_TOKEN=$(get_token)
# Build filter string for images
FILTERS=()
if [[ -n "$REPOSITORY" ]]; then
FILTERS+=("repository:'$REPOSITORY'")
fi
if [[ -n "$REGISTRY" ]]; then
FILTERS+=("registry:'$REGISTRY'")
fi
if [[ -n "$TAG" ]]; then
FILTERS+=("tag:'$TAG'")
fi
if [[ -n "$IMAGE_DIGEST" ]]; then
FILTERS+=("image_digest:'$IMAGE_DIGEST'")
fi
FILTER_STRING=""
if [[ ${#FILTERS[@]} -gt 0 ]]; then
FILTER_STRING=$(IFS='+'; echo "${FILTERS[*]}")
fi
# Get image details
IMAGE_RESPONSE=$(get_image_details "$FILTER_STRING" "$LIMIT")
# Check for errors
if echo "$IMAGE_RESPONSE" | jq -e '.errors | length > 0' > /dev/null 2>&1; then
echo "$IMAGE_RESPONSE" | jq '{error: "API Error", details: .errors}'
exit 1
fi
# Handle different output formats
case $OUTPUT_FORMAT in
"images-only")
echo "$IMAGE_RESPONSE"
exit 0
;;
"packages-only")
# Get first image digest and return only packages
FIRST_DIGEST=$(echo "$IMAGE_RESPONSE" | jq -r '.resources[0].digest // empty')
if [[ -n "$FIRST_DIGEST" ]]; then
get_image_packages "$FIRST_DIGEST" "$PACKAGE_LIMIT"
else
echo '{"error": "No images found or no digest available", "resources": []}'
fi
exit 0
;;
"combined")
# Default: combine images with their packages
;;
*)
echo '{"error": "Invalid format. Use: combined, images-only, or packages-only"}' >&2
exit 1
;;
esac
# Process images and combine with packages
COMBINED_RESULT='{"images": []}'
# Process each image
IMAGES=$(echo "$IMAGE_RESPONSE" | jq -r '.resources[] | @base64')
if [[ -z "$IMAGES" ]]; then
echo '{"images": [], "message": "No images found matching the criteria"}'
exit 0
fi
for image_data in $IMAGES; do
# Decode image data
IMAGE_JSON=$(echo "$image_data" | base64 --decode)
# Get digest
DIGEST=$(echo "$IMAGE_JSON" | jq -r '.digest // ""')
if [[ -n "$DIGEST" && "$DIGEST" != "null" ]]; then
# Get packages for this image
PACKAGE_RESPONSE=$(get_image_packages "$DIGEST" "$PACKAGE_LIMIT")
# Check for package errors
if echo "$PACKAGE_RESPONSE" | jq -e '.errors | length > 0' > /dev/null 2>&1; then
PACKAGES='{"error": "Failed to get packages", "details": []}'
else
PACKAGES=$(echo "$PACKAGE_RESPONSE" | jq '.resources')
fi
else
PACKAGES='[]'
fi
# Combine image with packages
COMBINED_IMAGE=$(echo "$IMAGE_JSON" | jq --argjson packages "$PACKAGES" '. + {packages: $packages}')
# Add to result
COMBINED_RESULT=$(echo "$COMBINED_RESULT" | jq --argjson image "$COMBINED_IMAGE" '.images += [$image]')
done
# Add metadata
TOTAL_IMAGES=$(echo "$IMAGE_RESPONSE" | jq '.resources | length')
COMBINED_RESULT=$(echo "$COMBINED_RESULT" | jq --argjson total "$TOTAL_IMAGES" '. + {total_images: $total, generated_at: now | strftime("%Y-%m-%dT%H:%M:%SZ")}')
# Output final JSON
echo "$COMBINED_RESULT" | jq '.'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment