The Joern for cloud — builds an Asset Property Graph from AWS Config and exports Joern-compatible nodes.json + edges.json.
Bona discovers your cloud infrastructure via AWS Config's advanced SQL queries and produces a typed property graph that can be loaded into Amazon Neptune, the GraphRAG Toolkit, Neo4j, or any tool that consumes flat JSON node/edge format. The output schema is inspired by Joern's cpg_exporter.py — same flat structure, applied to cloud assets instead of code.
pip install bonaRequires Python 3.11+ and valid AWS credentials with config:SelectResourceConfig permissions.
# Export all resources in a region
bona export --profile myprofile --region us-east-1
# Filter to specific resource types (supports globs)
bona export --profile myprofile --region us-east-1 --types "AWS::EC2::*,AWS::S3::*"
# Multi-region export
bona export --profile myprofile --region us-east-1,us-west-2,eu-west-1
# Multi-account via aggregator
bona export --profile myprofile --region us-east-1 --aggregator MyOrgAggregator
# Include compliance findings as graph nodes
bona export --profile myprofile --region us-east-1 --include-compliance
# Upload to S3
bona export --profile myprofile --region us-east-1 --s3-uri s3://my-bucket/apg/
# Export for Neptune bulk loader
bona export --profile myprofile --region us-east-1 --format neptunecsv# Get configuration history for a specific resource
bona history --profile myprofile --region us-east-1 \
--resource-type AWS::EC2::Instance \
--resource-id i-0abc123def456
# Limit to last 5 snapshots
bona history --profile myprofile --region us-east-1 \
--resource-type AWS::S3::Bucket \
--resource-id my-bucket-name \
--limit 5# Show all resource types tracked by AWS Config in a region
bona types --profile myprofile --region us-east-1# Download CloudFormation type schemas to local cache
bona schema --profile myprofile --region us-east-1
# Specific types
bona schema --profile myprofile --region us-east-1 --types "AWS::S3::Bucket,AWS::EC2::Instance"
# All types tracked by Config
bona schema --profile myprofile --region us-east-1 --types all
# Upload schema cache to S3
bona schema --profile myprofile --region us-east-1 --s3-uri s3://my-bucket/schemas/# Export with type schema enrichment (soft = uses cache, no extra AWS calls)
bona export --profile myprofile --region us-east-1 --enrich
# Hard enrichment (re-downloads schemas from CloudFormation)
bona export --profile myprofile --region us-east-1 --enrich hardEnrichment adds TypeDefinition reference nodes and INSTANCE_OF edges to the graph:
[IAM Role "MyRole"] ──INSTANCE_OF──▶ [TypeDefinition "AWS::IAM::Role"]
[IAM Role "OtherRole"] ──INSTANCE_OF──▶ [TypeDefinition "AWS::IAM::Role"]
[S3 Bucket "data"] ──INSTANCE_OF──▶ [TypeDefinition "AWS::S3::Bucket"]
One TypeDefinition node per resource type carries the full schema contract (properties, required fields, read-only, identifiers).
Bona discovers your Amazon Bedrock environment — foundation models, agents, knowledge bases, prompts, guardrails, and AgentCore components:
bona export --profile myprofile --region us-east-1 --types "AWS::Bedrock::*"What it discovers: 119+ foundation models, providers (Anthropic, Meta, Amazon, etc.), modalities (TEXT, IMAGE, EMBEDDING), model families, Bedrock Agents, Prompt Flows, and AgentCore resources.
Graph structure — model decomposition:
[Model "claude-3-sonnet"] ──PROVIDED_BY──▶ [Provider "Anthropic"]
[Model "claude-3-sonnet"] ──HAS_MODALITY──▶ [Modality "TEXT"]
[Agent "support-bot"] ──USES_MODEL──▶ [Model "claude-3-sonnet"]
[Agent "support-bot"] ──HAS_KB──▶ [KnowledgeBase "product-docs"]
See docs/bedrock-discovery.md for full details.
Control enrichment, output, and logging via bona.yaml:
logging:
level: INFO # DEBUG | INFO | WARNING | ERROR
path: ./bona.log # local file or s3://bucket/logs/
enrichment:
enabled: true
mode: soft # soft | hard
include:
- "AWS::EC2::*"
- "AWS::S3::*"
exclude:
- "AWS::CloudFormation::Stack"
output:
dir: ./bona-output
s3_uri: s3://my-bucket/apg/
schema_cache:
s3_uri: s3://my-bucket/schemas/
learning:
s3_uri: s3://my-bucket/bona-learning/ # uploads graph-schema, rules, descriptionsPlace as ./bona.yaml (project-local), ~/.bona/config.yaml (user-level), or pass --config path/to/bona.yaml.
Learn mode maintains a versioned schema of every node type and edge type Bona has ever observed. Each export run updates the schema with new types, properties, and relationship pairs — so over time you get a complete picture of your graph's shape without manual maintenance.
# Learn from an export (writes/updates ~/.bona/graph-schema.json)
bona export --profile myprofile --region us-east-1 --learn
# Combine with enrichment
bona export --profile myprofile --region us-east-1 --enrich --learnThe schema lives at ~/.bona/graph-schema.json (configurable via schema.path in bona.yaml).
Semver auto-bumping:
- MINOR bump — new node types, edge types, or relationship pairs discovered
- PATCH bump — only observation counts/timestamps updated
Example — what the schema captures:
{
"version": "0.3.0",
"last_updated": "2026-08-06T12:00:00Z",
"node_types": {
"AWS::EC2::Instance": {
"observed_properties": ["imageId", "instanceType", "subnetId", "vpcId"],
"observed_count": 42,
"first_seen": "2026-08-01T10:00:00Z",
"last_seen": "2026-08-06T12:00:00Z"
}
},
"edge_types": {
"IS_CONTAINED_IN": {
"observed_pairs": [
{"source_type": "AWS::EC2::Subnet", "target_type": "AWS::EC2::VPC"}
],
"observed_count": 15
}
}
}See docs/graph-schema.md for full details.
LLM enrichment uses Amazon Bedrock to analyze resource properties and improve graph quality:
- Relationship classification — identifies properties that reference other resources and converts them into typed edges
- Description generation — produces human-readable summaries of resources for graph exploration and RAG
# Export with LLM enrichment
bona export --profile myprofile --region us-east-1 --llm-enrich
# Combine with schema enrichment and learn mode
bona export --profile myprofile --region us-east-1 --enrich --llm-enrich --learnConfiguration in bona.yaml:
llm_enrichment:
model_id: anthropic.claude-3-haiku-20240307-v1:0
tasks:
- classify_relationships
- generate_descriptions
budget:
max_invocations: 500 # cap per export run
max_input_tokens: 100000 # total input token budgetExample — what LLM enrichment discovers:
# A securityGroupIds property gets classified as a relationship:
[Lambda "api-handler"] ──IS_ASSOCIATED_WITH──▶ [SG "sg-abc123"]
# A resource gets a generated description:
{ "description": "Production API handler Lambda in VPC, triggered by API Gateway, with DynamoDB access" }
See docs/llm-enrichment.md for full details.
Bona uses a pluggy-based plugin system that lets you add new providers, supplemental enrichers, and relationship classifiers without modifying core bona code.
Install a plugin:
pip install bona-provider-xxxPlugins are auto-discovered via Python entry points — no config changes needed.
Create a plugin:
# my_provider/plugin.py
import bona.hookspecs
from pluggy import HookimplMarker
hookimpl = HookimplMarker("bona")
@hookimpl
def bona_reference_providers():
"""Return a list of ReferenceProvider instances."""
return [MyCustomProvider()]Register via pyproject.toml:
[project.entry-points."bona"]
my_provider = "my_provider.plugin"Available hooks:
| Hook | Purpose |
|---|---|
bona_reference_providers |
Add reference data providers (type definitions, external metadata) |
bona_supplemental_providers |
Add supplemental enrichment providers (hardware specs, pricing, etc.) |
bona_relationship_classifiers |
Add custom relationship classification logic |
See examples/bona-provider-example/ for a full working plugin example.
bona [--verbose] [--config <path>] <command> [options]
Global:
--verbose, -v Enable debug logging (overrides config level to DEBUG)
--config <path> Path to bona config file (JSON or YAML)
Commands:
export Export Asset Property Graph (nodes.json + edges.json)
schema Download CloudFormation type schemas to local cache
history Export temporal graph — resource config over time
types List resource types tracked by AWS Config
cache Pre-populate local cache (instance types, etc.)
| Flag | Default | Description |
|---|---|---|
--profile |
(default) | AWS profile name from ~/.aws/credentials |
--region |
us-east-1 | AWS region (comma-separated for multi-region) |
--output-dir |
./bona-output | Local output directory |
--s3-uri |
S3 URI for upload (e.g. s3://bucket/prefix/) |
|
--types |
(all) | Resource type filter (comma-separated, globs supported) |
--max-resources |
10000 | Safety cap on total resources |
--include-compliance |
false | Include Config rule compliance findings as nodes |
--aggregator |
Config aggregator name for multi-account queries | |
--enrich |
disabled | Enable type schema enrichment (soft or hard). If flag given without value: soft |
--learn |
false | Update graph schema with discovered node/edge types (writes ~/.bona/graph-schema.json) |
--llm-enrich |
false | Enable LLM enrichment via Bedrock (classify relationships, generate descriptions) |
--format |
json | Output format: json, neptunecsv, neo4jcsv, graphml, dot |
--refresh-cache |
false | Force refresh of all cached data from AWS |
| Flag | Default | Description |
|---|---|---|
--profile |
(default) | AWS profile name |
--region |
us-east-1 | AWS region |
--type |
Single resource type (e.g. AWS::S3::Bucket) |
|
--types |
(common) | Comma-separated types, or all for Config-tracked types |
--s3-uri |
Upload schemas to S3 (e.g. s3://bucket/schemas/) |
|
--validate |
Validate a nodes.json file against cached schemas |
| Flag | Default | Description |
|---|---|---|
--profile |
(default) | AWS profile name |
--region |
us-east-1 | AWS region |
--resource-type |
(required) | Resource type (e.g. AWS::EC2::Instance) |
--resource-id |
(required) | Resource ID |
--limit |
20 | Max history items |
--output-dir |
./bona-output | Local output directory |
| Flag | Default | Description |
|---|---|---|
--profile |
(default) | AWS profile name |
--region |
us-east-1 | AWS region |
Bona produces two files: nodes.json and edges.json (plus a manifest.json summary).
Each node is a flat JSON object representing an AWS resource:
[
{
"id": "arn:aws:ec2:us-east-1:123456789012:instance/i-0abc123def456",
"node_type": "AWS::EC2::Instance",
"name": "web-server-1",
"stable_id": "aws|123456789012|us-east-1|AWS::EC2::Instance|i-0abc123def456",
"semantic_hash": "a1b2c3d4e5f6g7h8",
"provider": "aws",
"account_id": "123456789012",
"region": "us-east-1",
"resource_type": "AWS::EC2::Instance",
"state": "ACTIVE",
"tags": {"Name": "web-server-1", "Environment": "production"},
"configuration_capture_time": "2026-08-05T12:00:00Z",
"instanceType": "t3.medium",
"vpcId": "vpc-abc123",
"subnetId": "subnet-def456",
"imageId": "ami-0123456789abcdef0"
}
]Key fields:
- id — ARN (globally unique identifier)
- node_type — AWS resource type (e.g.
AWS::EC2::Instance) - stable_id — deterministic identity for delta reconciliation across runs
- semantic_hash — SHA-256 of configuration; detects config drift
- state —
ACTIVE,DELETED, orDISCOVERED - All configuration properties are flattened to top-level keys (Joern style)
Each edge represents a relationship between resources:
[
{
"source_id": "arn:aws:ec2:us-east-1:123456789012:instance/i-0abc123",
"target_id": "arn:aws:ec2:us-east-1:123456789012:security-group/sg-xyz789",
"edge_type": "IS_ASSOCIATED_WITH",
"stable_id": "aws|123456789012|EDGE|IS_ASSOCIATED_WITH|i-0abc123→sg-xyz789"
}
]Edge types include:
IS_CONTAINED_IN,CONTAINS— hierarchy (subnet in VPC)IS_ATTACHED_TO— resource attachments (volume to instance)IS_ASSOCIATED_WITH— associations (security group to instance)IN_VPC,IN_SUBNET— inferred from configuration propertiesUSES_ROLE,ENCRYPTED_BY— inferred from IAM/KMS referencesROUTES_TO,ATTACHED_TO,IN_CLUSTER— inferred from ARN referencesINSTANCE_OF— resource instance to its TypeDefinition (enrichment)RUNS_ON— EC2 instance to its InstanceSpec (supplemental enrichment)CHANGED_TO— temporal edges (history command)
graph TD
CLI[bona CLI] --> Config[Config<br/>bona.yaml]
CLI --> Provider[AWSProvider]
Provider --> Enumerate[1. Enumerate types]
Provider --> Query[2. SQL query + backfill]
Provider --> Extract[3. Extract relationships]
Provider --> Enrich[4. Enrich via plugins]
Provider --> Learn[5. Learn mode]
Provider --> LLM[6. LLM enrichment]
Enrich --> Plugins[Plugin Manager<br/>pluggy]
Plugins --> TypeSchemas[Type Schemas<br/>CloudFormation]
Plugins --> RefProviders[Reference Providers<br/>Bedrock, EC2 specs]
Plugins --> External[External Plugins<br/>pip install]
Provider --> Exporter[Exporter]
Exporter --> NodesJSON[nodes.json]
Exporter --> EdgesJSON[edges.json]
Exporter --> S3[S3 Upload]
Learn --> GraphSchema[graph-schema.json<br/>semver]
Learn --> Rules[relationship-rules.json]
LLM --> Descriptions[descriptions.json]
subgraph AWS APIs
ConfigAPI[AWS Config<br/>select_resource_config<br/>batch_get_resource_config]
CFN[CloudFormation<br/>describe_type]
Bedrock[Bedrock<br/>list_foundation_models<br/>bedrock-agent<br/>bedrock-agentcore]
BedrockRT[Bedrock Runtime<br/>Converse API]
end
Query --> ConfigAPI
TypeSchemas --> CFN
RefProviders --> Bedrock
LLM --> BedrockRT
subgraph Consumers
Neptune[Amazon Neptune]
GraphRAG[GraphRAG Toolkit]
Neo4j[Neo4j]
Analytics[jq / pandas]
end
Exporter --> Consumers
Use the --aggregator flag to query across multiple AWS accounts and regions using an AWS Config Aggregator:
bona export --profile management --region us-east-1 --aggregator MyOrgAggregatorThis uses select_aggregate_resource_config instead of select_resource_config, returning resources from all accounts enrolled in the aggregator. Each node retains its original account_id and region.
See docs/multi-account.md for aggregator setup instructions.
| Consumer | How to use |
|---|---|
| Amazon Neptune | bona export --format neptunecsv → upload CSVs to S3 → Neptune bulk loader |
| GraphRAG Toolkit | Feed nodes.json as document-graph source for graph-augmented RAG |
| Neo4j | bona export --format neo4jcsv or import JSON via apoc.load.json |
| document-graph | Native format — flat JSON nodes with typed edges |
| jq / pandas | Standard JSON — pipe through jq or load with pd.read_json() |
Bona integrates with MCP (Model Context Protocol) servers for two purposes:
When MCP is enabled, Claude can call AWS APIs during reasoning to make better classification decisions:
mcp:
enabled: true
servers:
- name: aws
server_url: "http://localhost:3000"
- name: cmdb
command: "npx @company/cmdb-mcp-server"
tool_filter: [lookup_asset, get_owner]Discover resources from any AWS service without writing a plugin:
mcp:
enabled: true
servers:
- name: aws
server_url: "http://localhost:3000"
discovery:
- service: guardduty
operation: ListDetectors
node_type: "AWS::GuardDuty::Detector"
id_field: "DetectorIds[]"
detail_operation: GetDetector
detail_params: {DetectorId: "$id"}
- service: securityhub
operation: GetFindings
node_type: "AWS::SecurityHub::Finding"
items_key: Findings
id_field: Id
name_field: TitleSee docs/llm-enrichment.md for full MCP documentation.
Bona discovers Google Cloud resources via Cloud Asset Inventory:
# Configure in bona.yaml
gcp:
project_id: "my-project-id"
credentials: "~/.bona/gcp-key.json"from bona.providers.gcp import GCPProvider
provider = GCPProvider(project_id="my-project", credentials="~/.bona/gcp-key.json")
result = provider.discover_with_enrichment()
# Returns: resources + IAM edges + Recommender insights + hierarchy nodesWhat it discovers:
- All resources via Cloud Asset Inventory (Storage, Compute, Functions, PubSub, etc.)
- IAM policy bindings as
HAS_ROLEedges (who can access what) - Recommender insights (over-permissioned accounts, idle resources)
- Security Command Center findings (requires GCP Organization)
Graph structure:
Cloud::Root → Cloud::Provider (GCP) → Cloud::Project → Cloud::Region
├── GCP::Storage::Bucket ──BELONGS_TO──▶ Region
├── serviceAccount:sa@proj ──HAS_ROLE(viewer)──▶ Project
└── GCP::Recommender::Insight ◀──HAS_INSIGHT── over-permissioned account
Compare two exports to detect infrastructure changes:
# Text summary
bona diff ./export-monday ./export-friday
# JSON output (for CI/CD pipelines)
bona diff ./baseline ./current --format json
# Show only modified resources with property-level changes
bona diff ./old ./new --only modifiedDrift detection uses stable_id for identity matching and semantic_hash for change detection — so renamed resources are tracked correctly across exports.
- Python 3.11+
- AWS credentials with permissions:
config:SelectResourceConfigconfig:SelectAggregateResourceConfig(if using--aggregator)config:GetResourceConfigHistory(forhistorycommand)config:DescribeComplianceByResource(if using--include-compliance)config:DescribeConfigRules(if using--include-compliance)cloudformation:DescribeType(if using--enrichorbona schema)ec2:DescribeInstanceTypes(if using EC2 supplemental enrichment)bedrock-runtime:Converse(if using--llm-enrich)sts:GetCallerIdentitys3:PutObject(if using--s3-uri)
- AWS Config recorder must be enabled in target region(s)
MIT