cartography 0.104.0rc2__py3-none-any.whl → 0.123.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cartography/_version.py +16 -3
- cartography/cli.py +466 -5
- cartography/client/aws/__init__.py +19 -0
- cartography/client/aws/ecr.py +51 -0
- cartography/client/core/tx.py +357 -8
- cartography/config.py +153 -0
- cartography/data/azure_permission_relationships.yaml +20 -0
- cartography/data/gcp_permission_relationships.yaml +21 -0
- cartography/data/indexes.cypher +0 -186
- cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
- cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
- cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
- cartography/data/jobs/cleanup/github_repos_cleanup.json +2 -0
- cartography/driftdetect/cli.py +3 -2
- cartography/graph/cleanupbuilder.py +198 -41
- cartography/graph/job.py +54 -6
- cartography/graph/querybuilder.py +528 -27
- cartography/graph/statement.py +5 -1
- cartography/intel/airbyte/__init__.py +105 -0
- cartography/intel/airbyte/connections.py +120 -0
- cartography/intel/airbyte/destinations.py +81 -0
- cartography/intel/airbyte/organizations.py +59 -0
- cartography/intel/airbyte/sources.py +78 -0
- cartography/intel/airbyte/tags.py +64 -0
- cartography/intel/airbyte/users.py +106 -0
- cartography/intel/airbyte/util.py +122 -0
- cartography/intel/airbyte/workspaces.py +63 -0
- cartography/intel/aws/__init__.py +24 -9
- cartography/intel/aws/acm.py +124 -0
- cartography/intel/aws/apigateway.py +253 -22
- cartography/intel/aws/apigatewayv2.py +116 -0
- cartography/intel/aws/cloudtrail.py +17 -39
- cartography/intel/aws/cloudtrail_management_events.py +962 -0
- cartography/intel/aws/cloudwatch.py +150 -4
- cartography/intel/aws/codebuild.py +132 -0
- cartography/intel/aws/cognito.py +201 -0
- cartography/intel/aws/config.py +7 -3
- cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
- cartography/intel/aws/ec2/instances.py +25 -1
- cartography/intel/aws/ec2/internet_gateways.py +4 -2
- cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
- cartography/intel/aws/ec2/network_interfaces.py +5 -1
- cartography/intel/aws/ec2/reserved_instances.py +3 -1
- cartography/intel/aws/ec2/security_groups.py +140 -122
- cartography/intel/aws/ec2/snapshots.py +47 -84
- cartography/intel/aws/ec2/subnets.py +37 -63
- cartography/intel/aws/ec2/tgw.py +11 -5
- cartography/intel/aws/ec2/volumes.py +1 -1
- cartography/intel/aws/ec2/vpc.py +140 -124
- cartography/intel/aws/ec2/vpc_peerings.py +262 -125
- cartography/intel/aws/ecr.py +269 -98
- cartography/intel/aws/ecr_image_layers.py +923 -0
- cartography/intel/aws/ecs.py +251 -380
- cartography/intel/aws/efs.py +179 -11
- cartography/intel/aws/elasticache.py +102 -79
- cartography/intel/aws/elasticsearch.py +13 -4
- cartography/intel/aws/eventbridge.py +164 -0
- cartography/intel/aws/glue.py +181 -0
- cartography/intel/aws/guardduty.py +443 -0
- cartography/intel/aws/iam.py +750 -493
- cartography/intel/aws/identitycenter.py +605 -83
- cartography/intel/aws/inspector.py +221 -105
- cartography/intel/aws/kms.py +173 -201
- cartography/intel/aws/lambda_function.py +272 -189
- cartography/intel/aws/organizations.py +10 -9
- cartography/intel/aws/permission_relationships.py +10 -20
- cartography/intel/aws/rds.py +337 -446
- cartography/intel/aws/redshift.py +9 -4
- cartography/intel/aws/resourcegroupstaggingapi.py +78 -19
- cartography/intel/aws/resources.py +18 -0
- cartography/intel/aws/route53.py +386 -332
- cartography/intel/aws/s3.py +322 -14
- cartography/intel/aws/secretsmanager.py +81 -49
- cartography/intel/aws/securityhub.py +3 -1
- cartography/intel/aws/sns.py +62 -2
- cartography/intel/aws/sqs.py +36 -90
- cartography/intel/aws/ssm.py +3 -5
- cartography/intel/azure/__init__.py +202 -48
- cartography/intel/azure/aks.py +175 -0
- cartography/intel/azure/app_service.py +105 -0
- cartography/intel/azure/compute.py +59 -112
- cartography/intel/azure/container_instances.py +95 -0
- cartography/intel/azure/cosmosdb.py +222 -361
- cartography/intel/azure/data_factory.py +85 -0
- cartography/intel/azure/data_factory_dataset.py +128 -0
- cartography/intel/azure/data_factory_linked_service.py +119 -0
- cartography/intel/azure/data_factory_pipeline.py +142 -0
- cartography/intel/azure/data_lake.py +124 -0
- cartography/intel/azure/event_grid.py +94 -0
- cartography/intel/azure/functions.py +124 -0
- cartography/intel/azure/load_balancers.py +263 -0
- cartography/intel/azure/logic_apps.py +101 -0
- cartography/intel/azure/monitor.py +105 -0
- cartography/intel/azure/network.py +467 -0
- cartography/intel/azure/permission_relationships.py +466 -0
- cartography/intel/azure/rbac.py +309 -0
- cartography/intel/azure/resource_groups.py +82 -0
- cartography/intel/azure/security_center.py +106 -0
- cartography/intel/azure/sql.py +145 -292
- cartography/intel/azure/storage.py +185 -262
- cartography/intel/azure/subscription.py +21 -43
- cartography/intel/azure/tenant.py +39 -30
- cartography/intel/azure/util/common.py +13 -0
- cartography/intel/azure/util/credentials.py +49 -174
- cartography/intel/azure/util/tag.py +41 -0
- cartography/intel/create_indexes.py +2 -1
- cartography/intel/crowdstrike/spotlight.py +5 -2
- cartography/intel/dns.py +5 -2
- cartography/intel/entra/__init__.py +100 -1
- cartography/intel/entra/app_role_assignments.py +284 -0
- cartography/intel/entra/applications.py +182 -0
- cartography/intel/entra/federation/__init__.py +0 -0
- cartography/intel/entra/federation/aws_identity_center.py +77 -0
- cartography/intel/entra/groups.py +198 -0
- cartography/intel/entra/ou.py +48 -24
- cartography/intel/entra/service_principals.py +217 -0
- cartography/intel/entra/users.py +105 -57
- cartography/intel/gcp/__init__.py +334 -396
- cartography/intel/gcp/bigtable_app_profile.py +101 -0
- cartography/intel/gcp/bigtable_backup.py +91 -0
- cartography/intel/gcp/bigtable_cluster.py +93 -0
- cartography/intel/gcp/bigtable_instance.py +86 -0
- cartography/intel/gcp/bigtable_table.py +87 -0
- cartography/intel/gcp/cai.py +292 -0
- cartography/intel/gcp/clients.py +112 -0
- cartography/intel/gcp/compute.py +128 -119
- cartography/intel/gcp/crm/__init__.py +0 -0
- cartography/intel/gcp/crm/folders.py +114 -0
- cartography/intel/gcp/crm/orgs.py +70 -0
- cartography/intel/gcp/crm/projects.py +120 -0
- cartography/intel/gcp/dns.py +83 -169
- cartography/intel/gcp/gke.py +72 -113
- cartography/intel/gcp/iam.py +111 -91
- cartography/intel/gcp/permission_relationships.py +394 -0
- cartography/intel/gcp/policy_bindings.py +225 -0
- cartography/intel/gcp/storage.py +75 -159
- cartography/intel/github/__init__.py +62 -25
- cartography/intel/github/commits.py +423 -0
- cartography/intel/github/repos.py +463 -85
- cartography/intel/github/teams.py +3 -3
- cartography/intel/github/users.py +5 -0
- cartography/intel/github/util.py +12 -0
- cartography/intel/googleworkspace/__init__.py +193 -0
- cartography/intel/googleworkspace/devices.py +254 -0
- cartography/intel/googleworkspace/groups.py +568 -0
- cartography/intel/googleworkspace/oauth_apps.py +259 -0
- cartography/intel/googleworkspace/tenant.py +85 -0
- cartography/intel/googleworkspace/users.py +138 -0
- cartography/intel/gsuite/__init__.py +17 -9
- cartography/intel/gsuite/groups.py +291 -0
- cartography/intel/gsuite/users.py +142 -0
- cartography/intel/jamf/computers.py +7 -1
- cartography/intel/keycloak/__init__.py +153 -0
- cartography/intel/keycloak/authenticationexecutions.py +322 -0
- cartography/intel/keycloak/authenticationflows.py +77 -0
- cartography/intel/keycloak/clients.py +187 -0
- cartography/intel/keycloak/groups.py +126 -0
- cartography/intel/keycloak/identityproviders.py +94 -0
- cartography/intel/keycloak/organizations.py +163 -0
- cartography/intel/keycloak/realms.py +61 -0
- cartography/intel/keycloak/roles.py +202 -0
- cartography/intel/keycloak/scopes.py +73 -0
- cartography/intel/keycloak/users.py +70 -0
- cartography/intel/keycloak/util.py +47 -0
- cartography/intel/kubernetes/__init__.py +60 -14
- cartography/intel/kubernetes/clusters.py +86 -0
- cartography/intel/kubernetes/eks.py +402 -0
- cartography/intel/kubernetes/namespaces.py +59 -57
- cartography/intel/kubernetes/pods.py +168 -75
- cartography/intel/kubernetes/rbac.py +597 -0
- cartography/intel/kubernetes/secrets.py +95 -45
- cartography/intel/kubernetes/services.py +131 -67
- cartography/intel/kubernetes/util.py +142 -14
- cartography/intel/oci/iam.py +23 -9
- cartography/intel/oci/organizations.py +3 -1
- cartography/intel/oci/utils.py +28 -5
- cartography/intel/okta/applications.py +15 -5
- cartography/intel/okta/awssaml.py +14 -10
- cartography/intel/okta/factors.py +3 -1
- cartography/intel/okta/groups.py +5 -2
- cartography/intel/okta/organization.py +3 -1
- cartography/intel/okta/origins.py +3 -1
- cartography/intel/okta/roles.py +5 -2
- cartography/intel/okta/users.py +10 -2
- cartography/intel/ontology/__init__.py +44 -0
- cartography/intel/ontology/devices.py +54 -0
- cartography/intel/ontology/users.py +54 -0
- cartography/intel/ontology/utils.py +176 -0
- cartography/intel/pagerduty/escalation_policies.py +13 -6
- cartography/intel/pagerduty/schedules.py +9 -4
- cartography/intel/pagerduty/services.py +7 -3
- cartography/intel/pagerduty/teams.py +5 -2
- cartography/intel/pagerduty/users.py +3 -1
- cartography/intel/pagerduty/vendors.py +3 -1
- cartography/intel/scaleway/__init__.py +127 -0
- cartography/intel/scaleway/iam/__init__.py +0 -0
- cartography/intel/scaleway/iam/apikeys.py +71 -0
- cartography/intel/scaleway/iam/applications.py +71 -0
- cartography/intel/scaleway/iam/groups.py +71 -0
- cartography/intel/scaleway/iam/users.py +71 -0
- cartography/intel/scaleway/instances/__init__.py +0 -0
- cartography/intel/scaleway/instances/flexibleips.py +86 -0
- cartography/intel/scaleway/instances/instances.py +92 -0
- cartography/intel/scaleway/projects.py +79 -0
- cartography/intel/scaleway/storage/__init__.py +0 -0
- cartography/intel/scaleway/storage/snapshots.py +86 -0
- cartography/intel/scaleway/storage/volumes.py +84 -0
- cartography/intel/scaleway/utils.py +37 -0
- cartography/intel/sentinelone/__init__.py +75 -0
- cartography/intel/sentinelone/account.py +140 -0
- cartography/intel/sentinelone/agent.py +139 -0
- cartography/intel/sentinelone/api.py +124 -0
- cartography/intel/sentinelone/application.py +248 -0
- cartography/intel/sentinelone/cve.py +119 -0
- cartography/intel/sentinelone/utils.py +28 -0
- cartography/intel/slack/__init__.py +78 -0
- cartography/intel/slack/channels.py +80 -0
- cartography/intel/slack/groups.py +90 -0
- cartography/intel/slack/teams.py +65 -0
- cartography/intel/slack/users.py +57 -0
- cartography/intel/slack/utils.py +29 -0
- cartography/intel/spacelift/__init__.py +161 -0
- cartography/intel/spacelift/account.py +73 -0
- cartography/intel/spacelift/ec2_ownership.py +280 -0
- cartography/intel/spacelift/runs.py +463 -0
- cartography/intel/spacelift/spaces.py +112 -0
- cartography/intel/spacelift/stacks.py +119 -0
- cartography/intel/spacelift/util.py +122 -0
- cartography/intel/spacelift/workerpools.py +131 -0
- cartography/intel/spacelift/workers.py +128 -0
- cartography/intel/trivy/__init__.py +272 -0
- cartography/intel/trivy/scanner.py +386 -0
- cartography/models/airbyte/__init__.py +0 -0
- cartography/models/airbyte/connection.py +138 -0
- cartography/models/airbyte/destination.py +75 -0
- cartography/models/airbyte/organization.py +19 -0
- cartography/models/airbyte/source.py +75 -0
- cartography/models/airbyte/stream.py +74 -0
- cartography/models/airbyte/tag.py +69 -0
- cartography/models/airbyte/user.py +115 -0
- cartography/models/airbyte/workspace.py +46 -0
- cartography/models/anthropic/apikey.py +4 -0
- cartography/models/anthropic/user.py +4 -0
- cartography/models/aws/acm/__init__.py +0 -0
- cartography/models/aws/acm/certificate.py +75 -0
- cartography/models/aws/apigateway/__init__.py +0 -0
- cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
- cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
- cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
- cartography/models/aws/apigatewayv2/__init__.py +0 -0
- cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
- cartography/models/aws/cloudtrail/management_events.py +153 -0
- cartography/models/aws/cloudtrail/trail.py +45 -0
- cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
- cartography/models/aws/cloudwatch/metric_alarm.py +53 -0
- cartography/models/aws/codebuild/__init__.py +0 -0
- cartography/models/aws/codebuild/project.py +49 -0
- cartography/models/aws/cognito/__init__.py +0 -0
- cartography/models/aws/cognito/identity_pool.py +70 -0
- cartography/models/aws/cognito/user_pool.py +47 -0
- cartography/models/aws/dynamodb/tables.py +2 -0
- cartography/models/aws/ec2/instances.py +25 -1
- cartography/models/aws/ec2/networkinterfaces.py +4 -0
- cartography/models/aws/ec2/security_group_rules.py +109 -0
- cartography/models/aws/ec2/security_groups.py +90 -0
- cartography/models/aws/ec2/snapshots.py +58 -0
- cartography/models/aws/ec2/subnet_instance.py +2 -0
- cartography/models/aws/ec2/subnet_networkinterface.py +2 -0
- cartography/models/aws/ec2/subnets.py +65 -0
- cartography/models/aws/ec2/volumes.py +20 -0
- cartography/models/aws/ec2/vpc.py +46 -0
- cartography/models/aws/ec2/vpc_cidr.py +102 -0
- cartography/models/aws/ec2/vpc_peering.py +157 -0
- cartography/models/aws/ecr/__init__.py +0 -0
- cartography/models/aws/ecr/image.py +146 -0
- cartography/models/aws/ecr/image_layer.py +107 -0
- cartography/models/aws/ecr/repository.py +72 -0
- cartography/models/aws/ecr/repository_image.py +95 -0
- cartography/models/aws/ecs/__init__.py +0 -0
- cartography/models/aws/ecs/clusters.py +64 -0
- cartography/models/aws/ecs/container_definitions.py +93 -0
- cartography/models/aws/ecs/container_instances.py +84 -0
- cartography/models/aws/ecs/containers.py +101 -0
- cartography/models/aws/ecs/services.py +134 -0
- cartography/models/aws/ecs/task_definitions.py +135 -0
- cartography/models/aws/ecs/tasks.py +134 -0
- cartography/models/aws/efs/access_point.py +77 -0
- cartography/models/aws/efs/file_system.py +60 -0
- cartography/models/aws/efs/mount_target.py +29 -2
- cartography/models/aws/elasticache/__init__.py +0 -0
- cartography/models/aws/elasticache/cluster.py +65 -0
- cartography/models/aws/elasticache/topic.py +67 -0
- cartography/models/aws/eventbridge/__init__.py +0 -0
- cartography/models/aws/eventbridge/rule.py +77 -0
- cartography/models/aws/eventbridge/target.py +71 -0
- cartography/models/aws/glue/__init__.py +0 -0
- cartography/models/aws/glue/connection.py +51 -0
- cartography/models/aws/glue/job.py +69 -0
- cartography/models/aws/guardduty/__init__.py +1 -0
- cartography/models/aws/guardduty/detectors.py +50 -0
- cartography/models/aws/guardduty/findings.py +121 -0
- cartography/models/aws/iam/access_key.py +103 -0
- cartography/models/aws/iam/account_role.py +24 -0
- cartography/models/aws/iam/federated_principal.py +60 -0
- cartography/models/aws/iam/group.py +60 -0
- cartography/models/aws/iam/group_membership.py +27 -0
- cartography/models/aws/iam/inline_policy.py +78 -0
- cartography/models/aws/iam/managed_policy.py +51 -0
- cartography/models/aws/iam/policy_statement.py +57 -0
- cartography/models/aws/iam/role.py +83 -0
- cartography/models/aws/iam/root_principal.py +52 -0
- cartography/models/aws/iam/service_principal.py +30 -0
- cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
- cartography/models/aws/iam/user.py +59 -0
- cartography/models/aws/identitycenter/awsidentitycenter.py +1 -0
- cartography/models/aws/identitycenter/awspermissionset.py +70 -0
- cartography/models/aws/identitycenter/awssogroup.py +70 -0
- cartography/models/aws/identitycenter/awsssouser.py +49 -9
- cartography/models/aws/inspector/findings.py +37 -0
- cartography/models/aws/inspector/packages.py +1 -31
- cartography/models/aws/kms/__init__.py +0 -0
- cartography/models/aws/kms/aliases.py +86 -0
- cartography/models/aws/kms/grants.py +65 -0
- cartography/models/aws/kms/keys.py +88 -0
- cartography/models/aws/lambda_function/__init__.py +0 -0
- cartography/models/aws/lambda_function/alias.py +74 -0
- cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
- cartography/models/aws/lambda_function/lambda_function.py +91 -0
- cartography/models/aws/lambda_function/layer.py +72 -0
- cartography/models/aws/rds/__init__.py +0 -0
- cartography/models/aws/rds/cluster.py +91 -0
- cartography/models/aws/rds/event_subscription.py +146 -0
- cartography/models/aws/rds/instance.py +156 -0
- cartography/models/aws/rds/snapshot.py +108 -0
- cartography/models/aws/rds/subnet_group.py +101 -0
- cartography/models/aws/route53/__init__.py +0 -0
- cartography/models/aws/route53/dnsrecord.py +235 -0
- cartography/models/aws/route53/nameserver.py +63 -0
- cartography/models/aws/route53/subzone.py +40 -0
- cartography/models/aws/route53/zone.py +47 -0
- cartography/models/aws/s3/notification.py +24 -0
- cartography/models/aws/secretsmanager/secret.py +106 -0
- cartography/models/aws/secretsmanager/secret_version.py +0 -2
- cartography/models/aws/sns/topic_subscription.py +74 -0
- cartography/models/aws/sqs/__init__.py +0 -0
- cartography/models/aws/sqs/queue.py +89 -0
- cartography/models/azure/__init__.py +0 -0
- cartography/models/azure/aks_cluster.py +54 -0
- cartography/models/azure/aks_nodepool.py +54 -0
- cartography/models/azure/app_service.py +59 -0
- cartography/models/azure/container_instance.py +57 -0
- cartography/models/azure/cosmosdb/__init__.py +0 -0
- cartography/models/azure/cosmosdb/account.py +77 -0
- cartography/models/azure/cosmosdb/accountfailoverpolicy.py +77 -0
- cartography/models/azure/cosmosdb/cassandrakeyspace.py +82 -0
- cartography/models/azure/cosmosdb/cassandratable.py +81 -0
- cartography/models/azure/cosmosdb/corspolicy.py +74 -0
- cartography/models/azure/cosmosdb/dblocation.py +120 -0
- cartography/models/azure/cosmosdb/mongodbcollection.py +82 -0
- cartography/models/azure/cosmosdb/mongodbdatabase.py +78 -0
- cartography/models/azure/cosmosdb/privateendpointconnection.py +81 -0
- cartography/models/azure/cosmosdb/sqlcontainer.py +88 -0
- cartography/models/azure/cosmosdb/sqldatabase.py +78 -0
- cartography/models/azure/cosmosdb/tableresource.py +76 -0
- cartography/models/azure/cosmosdb/virtualnetworkrule.py +78 -0
- cartography/models/azure/data_factory/__init__.py +0 -0
- cartography/models/azure/data_factory/data_factory.py +51 -0
- cartography/models/azure/data_factory/data_factory_dataset.py +94 -0
- cartography/models/azure/data_factory/data_factory_linked_service.py +78 -0
- cartography/models/azure/data_factory/data_factory_pipeline.py +93 -0
- cartography/models/azure/data_lake_filesystem.py +51 -0
- cartography/models/azure/event_grid_topic.py +57 -0
- cartography/models/azure/function_app.py +59 -0
- cartography/models/azure/load_balancer/__init__.py +0 -0
- cartography/models/azure/load_balancer/load_balancer.py +49 -0
- cartography/models/azure/load_balancer/load_balancer_backend_pool.py +73 -0
- cartography/models/azure/load_balancer/load_balancer_frontend_ip.py +75 -0
- cartography/models/azure/load_balancer/load_balancer_inbound_nat_rule.py +78 -0
- cartography/models/azure/load_balancer/load_balancer_rule.py +108 -0
- cartography/models/azure/logic_apps.py +56 -0
- cartography/models/azure/monitor.py +54 -0
- cartography/models/azure/network_interface.py +112 -0
- cartography/models/azure/network_security_group.py +50 -0
- cartography/models/azure/permission_relationships.py +60 -0
- cartography/models/azure/principal.py +41 -0
- cartography/models/azure/public_ip_address.py +50 -0
- cartography/models/azure/rbac.py +268 -0
- cartography/models/azure/resource_groups.py +52 -0
- cartography/models/azure/security_center.py +50 -0
- cartography/models/azure/sql/__init__.py +0 -0
- cartography/models/azure/sql/databasethreatdetectionpolicy.py +85 -0
- cartography/models/azure/sql/elasticpool.py +77 -0
- cartography/models/azure/sql/failovergroup.py +73 -0
- cartography/models/azure/sql/recoverabledatabase.py +75 -0
- cartography/models/azure/sql/replicationlink.py +81 -0
- cartography/models/azure/sql/restorabledroppeddatabase.py +82 -0
- cartography/models/azure/sql/restorepoint.py +74 -0
- cartography/models/azure/sql/serveradadministrator.py +74 -0
- cartography/models/azure/sql/serverdnsalias.py +71 -0
- cartography/models/azure/sql/sqldatabase.py +85 -0
- cartography/models/azure/sql/sqlserver.py +50 -0
- cartography/models/azure/sql/transparentdataencryption.py +76 -0
- cartography/models/azure/storage/__init__.py +0 -0
- cartography/models/azure/storage/account.py +59 -0
- cartography/models/azure/storage/blobcontainer.py +85 -0
- cartography/models/azure/storage/blobservice.py +71 -0
- cartography/models/azure/storage/fileservice.py +71 -0
- cartography/models/azure/storage/fileshare.py +82 -0
- cartography/models/azure/storage/queue.py +71 -0
- cartography/models/azure/storage/queueservice.py +73 -0
- cartography/models/azure/storage/table.py +72 -0
- cartography/models/azure/storage/tableservice.py +73 -0
- cartography/models/azure/subnet.py +101 -0
- cartography/models/azure/subscription.py +47 -0
- cartography/models/azure/tags/__init__.py +0 -0
- cartography/models/azure/tags/storage_tag.py +40 -0
- cartography/models/azure/tags/tag.py +37 -0
- cartography/models/azure/tenant.py +17 -0
- cartography/models/azure/virtual_network.py +49 -0
- cartography/models/azure/vm/__init__.py +0 -0
- cartography/models/azure/vm/datadisk.py +80 -0
- cartography/models/azure/vm/disk.py +55 -0
- cartography/models/azure/vm/snapshot.py +56 -0
- cartography/models/azure/vm/virtualmachine.py +59 -0
- cartography/models/bigfix/bigfix_computer.py +1 -1
- cartography/models/cloudflare/member.py +4 -0
- cartography/models/core/common.py +1 -0
- cartography/models/core/nodes.py +15 -2
- cartography/models/core/relationships.py +44 -0
- cartography/models/crowdstrike/hosts.py +1 -1
- cartography/models/digitalocean/droplet.py +2 -0
- cartography/models/duo/endpoint.py +1 -1
- cartography/models/duo/phone.py +2 -2
- cartography/models/duo/user.py +4 -0
- cartography/models/entra/app_role_assignment.py +115 -0
- cartography/models/entra/application.py +49 -0
- cartography/models/entra/entra_user_to_aws_sso.py +41 -0
- cartography/models/entra/group.py +117 -0
- cartography/models/entra/service_principal.py +104 -0
- cartography/models/entra/user.py +42 -51
- cartography/models/gcp/__init__.py +0 -0
- cartography/models/gcp/bigtable/__init__.py +0 -0
- cartography/models/gcp/bigtable/app_profile.py +94 -0
- cartography/models/gcp/bigtable/backup.py +91 -0
- cartography/models/gcp/bigtable/cluster.py +73 -0
- cartography/models/gcp/bigtable/instance.py +52 -0
- cartography/models/gcp/bigtable/table.py +69 -0
- cartography/models/gcp/compute/__init__.py +0 -0
- cartography/models/gcp/compute/subnet.py +74 -0
- cartography/models/gcp/compute/vpc.py +50 -0
- cartography/models/gcp/crm/__init__.py +0 -0
- cartography/models/gcp/crm/folders.py +98 -0
- cartography/models/gcp/crm/organizations.py +21 -0
- cartography/models/gcp/crm/projects.py +100 -0
- cartography/models/gcp/dns.py +109 -0
- cartography/models/gcp/gke.py +69 -0
- cartography/models/gcp/iam.py +3 -0
- cartography/models/gcp/permission_relationships.py +61 -0
- cartography/models/gcp/policy_bindings.py +93 -0
- cartography/models/gcp/storage/__init__.py +0 -0
- cartography/models/gcp/storage/bucket.py +119 -0
- cartography/models/github/commits.py +63 -0
- cartography/models/github/dependencies.py +73 -0
- cartography/models/github/manifests.py +49 -0
- cartography/models/github/users.py +10 -0
- cartography/models/googleworkspace/__init__.py +0 -0
- cartography/models/googleworkspace/device.py +132 -0
- cartography/models/googleworkspace/group.py +382 -0
- cartography/models/googleworkspace/oauth_app.py +124 -0
- cartography/models/googleworkspace/tenant.py +30 -0
- cartography/models/googleworkspace/user.py +113 -0
- cartography/models/gsuite/__init__.py +0 -0
- cartography/models/gsuite/group.py +218 -0
- cartography/models/gsuite/tenant.py +29 -0
- cartography/models/gsuite/user.py +107 -0
- cartography/models/kandji/device.py +1 -2
- cartography/models/keycloak/__init__.py +0 -0
- cartography/models/keycloak/authenticationexecution.py +160 -0
- cartography/models/keycloak/authenticationflow.py +54 -0
- cartography/models/keycloak/client.py +179 -0
- cartography/models/keycloak/group.py +101 -0
- cartography/models/keycloak/identityprovider.py +89 -0
- cartography/models/keycloak/organization.py +116 -0
- cartography/models/keycloak/organizationdomain.py +73 -0
- cartography/models/keycloak/realm.py +173 -0
- cartography/models/keycloak/role.py +126 -0
- cartography/models/keycloak/scope.py +73 -0
- cartography/models/keycloak/user.py +55 -0
- cartography/models/kubernetes/__init__.py +0 -0
- cartography/models/kubernetes/clusterrolebindings.py +138 -0
- cartography/models/kubernetes/clusterroles.py +52 -0
- cartography/models/kubernetes/clusters.py +26 -0
- cartography/models/kubernetes/containers.py +133 -0
- cartography/models/kubernetes/groups.py +107 -0
- cartography/models/kubernetes/namespaces.py +51 -0
- cartography/models/kubernetes/oidc.py +51 -0
- cartography/models/kubernetes/pods.py +80 -0
- cartography/models/kubernetes/rolebindings.py +159 -0
- cartography/models/kubernetes/roles.py +76 -0
- cartography/models/kubernetes/secrets.py +79 -0
- cartography/models/kubernetes/serviceaccounts.py +77 -0
- cartography/models/kubernetes/services.py +108 -0
- cartography/models/kubernetes/users.py +105 -0
- cartography/models/lastpass/user.py +4 -0
- cartography/models/ontology/__init__.py +0 -0
- cartography/models/ontology/device.py +137 -0
- cartography/models/ontology/mapping/__init__.py +76 -0
- cartography/models/ontology/mapping/data/__init__.py +0 -0
- cartography/models/ontology/mapping/data/apikeys.py +93 -0
- cartography/models/ontology/mapping/data/computeinstance.py +95 -0
- cartography/models/ontology/mapping/data/containers.py +88 -0
- cartography/models/ontology/mapping/data/databases.py +182 -0
- cartography/models/ontology/mapping/data/devices.py +194 -0
- cartography/models/ontology/mapping/data/thirdpartyapps.py +140 -0
- cartography/models/ontology/mapping/data/useraccounts.py +416 -0
- cartography/models/ontology/mapping/data/users.py +63 -0
- cartography/models/ontology/mapping/specs.py +85 -0
- cartography/models/ontology/user.py +51 -0
- cartography/models/openai/adminapikey.py +4 -0
- cartography/models/openai/apikey.py +4 -0
- cartography/models/openai/user.py +4 -0
- cartography/models/scaleway/__init__.py +0 -0
- cartography/models/scaleway/iam/__init__.py +0 -0
- cartography/models/scaleway/iam/apikey.py +100 -0
- cartography/models/scaleway/iam/application.py +52 -0
- cartography/models/scaleway/iam/group.py +95 -0
- cartography/models/scaleway/iam/user.py +64 -0
- cartography/models/scaleway/instance/__init__.py +0 -0
- cartography/models/scaleway/instance/flexibleip.py +52 -0
- cartography/models/scaleway/instance/instance.py +120 -0
- cartography/models/scaleway/organization.py +19 -0
- cartography/models/scaleway/project.py +48 -0
- cartography/models/scaleway/storage/__init__.py +0 -0
- cartography/models/scaleway/storage/snapshot.py +78 -0
- cartography/models/scaleway/storage/volume.py +51 -0
- cartography/models/sentinelone/__init__.py +1 -0
- cartography/models/sentinelone/account.py +40 -0
- cartography/models/sentinelone/agent.py +50 -0
- cartography/models/sentinelone/application.py +44 -0
- cartography/models/sentinelone/application_version.py +96 -0
- cartography/models/sentinelone/cve.py +73 -0
- cartography/models/slack/__init__.py +0 -0
- cartography/models/slack/channels.py +92 -0
- cartography/models/slack/group.py +129 -0
- cartography/models/slack/team.py +22 -0
- cartography/models/slack/user.py +62 -0
- cartography/models/snipeit/asset.py +2 -0
- cartography/models/snipeit/user.py +4 -0
- cartography/models/spacelift/__init__.py +0 -0
- cartography/models/spacelift/cloudtrailevent.py +120 -0
- cartography/models/spacelift/run.py +162 -0
- cartography/models/spacelift/space.py +131 -0
- cartography/models/spacelift/spaceliftaccount.py +31 -0
- cartography/models/spacelift/spaceliftgitcommit.py +157 -0
- cartography/models/spacelift/stack.py +96 -0
- cartography/models/spacelift/user.py +63 -0
- cartography/models/spacelift/worker.py +97 -0
- cartography/models/spacelift/workerpool.py +90 -0
- cartography/models/tailscale/device.py +2 -1
- cartography/models/tailscale/user.py +6 -1
- cartography/models/trivy/__init__.py +0 -0
- cartography/models/trivy/findings.py +66 -0
- cartography/models/trivy/fix.py +66 -0
- cartography/models/trivy/package.py +71 -0
- cartography/rules/README.md +1 -0
- cartography/rules/__init__.py +0 -0
- cartography/rules/cli.py +261 -0
- cartography/rules/data/__init__.py +0 -0
- cartography/rules/data/rules/__init__.py +46 -0
- cartography/rules/data/rules/cloud_security_product_deactivated.py +49 -0
- cartography/rules/data/rules/compute_instance_exposed.py +51 -0
- cartography/rules/data/rules/database_instance_exposed.py +53 -0
- cartography/rules/data/rules/delegation_boundary_modifiable.py +90 -0
- cartography/rules/data/rules/identity_administration_privileges.py +100 -0
- cartography/rules/data/rules/inactive_user_active_accounts.py +48 -0
- cartography/rules/data/rules/malicious_npm_dependencies_shai_hulud.py +2222 -0
- cartography/rules/data/rules/mfa_missing.py +46 -0
- cartography/rules/data/rules/object_storage_public.py +100 -0
- cartography/rules/data/rules/policy_administration_privileges.py +104 -0
- cartography/rules/data/rules/unmanaged_accounts.py +43 -0
- cartography/rules/data/rules/workload_identity_admin_capabilities.py +193 -0
- cartography/rules/formatters.py +108 -0
- cartography/rules/runners.py +216 -0
- cartography/rules/spec/__init__.py +0 -0
- cartography/rules/spec/model.py +267 -0
- cartography/rules/spec/result.py +38 -0
- cartography/sync.py +25 -5
- cartography/util.py +101 -31
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/METADATA +61 -22
- cartography-0.123.0.dist-info/RECORD +856 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/entry_points.txt +1 -0
- cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
- cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
- cartography/data/jobs/cleanup/aws_import_ec2_security_groupinfo_cleanup.json +0 -24
- cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
- cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
- cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
- cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
- cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
- cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
- cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_import_snapshots_cleanup.json +0 -30
- cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
- cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
- cartography/data/jobs/cleanup/azure_cosmosdb_cassandra_keyspace_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_cosmosdb_cors_details.json +0 -15
- cartography/data/jobs/cleanup/azure_cosmosdb_mongodb_database_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_cosmosdb_sql_database_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_cosmosdb_table_resources_cleanup.json +0 -15
- cartography/data/jobs/cleanup/azure_database_account_cleanup.json +0 -85
- cartography/data/jobs/cleanup/azure_import_disks_cleanup.json +0 -15
- cartography/data/jobs/cleanup/azure_import_snapshots_cleanup.json +0 -15
- cartography/data/jobs/cleanup/azure_import_virtual_machines_cleanup.json +0 -25
- cartography/data/jobs/cleanup/azure_sql_server_cleanup.json +0 -125
- cartography/data/jobs/cleanup/azure_storage_account_cleanup.json +0 -95
- cartography/data/jobs/cleanup/azure_subscriptions_cleanup.json +0 -14
- cartography/data/jobs/cleanup/azure_tenant_cleanup.json +0 -9
- cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
- cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
- cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gcp_dns_cleanup.json +0 -29
- cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
- cartography/data/jobs/cleanup/gcp_storage_bucket_cleanup.json +0 -29
- cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
- cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
- cartography/intel/gcp/crm.py +0 -355
- cartography/intel/gsuite/api.py +0 -342
- cartography-0.104.0rc2.dist-info/RECORD +0 -455
- /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
- /cartography/models/aws/{apigateway.py → apigateway/apigateway.py} +0 -0
- /cartography/models/aws/{apigatewaycertificate.py → apigateway/apigatewaycertificate.py} +0 -0
- /cartography/models/aws/{apigatewayresource.py → apigateway/apigatewayresource.py} +0 -0
- /cartography/models/aws/{apigatewaystage.py → apigateway/apigatewaystage.py} +0 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/WHEEL +0 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/top_level.txt +0 -0
cartography/client/core/tx.py
CHANGED
|
@@ -1,17 +1,281 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from functools import partial
|
|
1
4
|
from typing import Any
|
|
5
|
+
from typing import Callable
|
|
2
6
|
from typing import Dict
|
|
3
7
|
from typing import List
|
|
4
8
|
from typing import Optional
|
|
5
9
|
from typing import Tuple
|
|
10
|
+
from typing import TypeVar
|
|
6
11
|
from typing import Union
|
|
7
12
|
|
|
13
|
+
import backoff
|
|
8
14
|
import neo4j
|
|
15
|
+
import neo4j.exceptions
|
|
9
16
|
|
|
10
17
|
from cartography.graph.querybuilder import build_create_index_queries
|
|
18
|
+
from cartography.graph.querybuilder import build_create_index_queries_for_matchlink
|
|
11
19
|
from cartography.graph.querybuilder import build_ingestion_query
|
|
20
|
+
from cartography.graph.querybuilder import build_matchlink_query
|
|
12
21
|
from cartography.models.core.nodes import CartographyNodeSchema
|
|
22
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
23
|
+
from cartography.util import backoff_handler
|
|
13
24
|
from cartography.util import batch
|
|
14
25
|
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
T = TypeVar("T")
|
|
29
|
+
|
|
30
|
+
_MAX_NETWORK_RETRIES = 5
|
|
31
|
+
_MAX_ENTITY_NOT_FOUND_RETRIES = 5
|
|
32
|
+
_NETWORK_EXCEPTIONS: tuple[type[BaseException], ...] = (
|
|
33
|
+
ConnectionResetError,
|
|
34
|
+
neo4j.exceptions.ServiceUnavailable,
|
|
35
|
+
neo4j.exceptions.SessionExpired,
|
|
36
|
+
neo4j.exceptions.TransientError,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_retryable_client_error(exc: Exception) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Determine if a ClientError should be retried.
|
|
43
|
+
|
|
44
|
+
EntityNotFound during concurrent write operations is a known transient error in Neo4j
|
|
45
|
+
that occurs due to the database's query execution pipeline design. When multiple threads
|
|
46
|
+
run concurrent MERGE/DELETE operations, one thread can delete an entity that another
|
|
47
|
+
thread has already referenced but hasn't locked yet.
|
|
48
|
+
|
|
49
|
+
Neo4j maintainers explicitly recommend retrying EntityNotFound errors during
|
|
50
|
+
multi-threaded operations, even though the driver classifies it as a non-retryable
|
|
51
|
+
ClientError. See: https://github.com/neo4j/neo4j/issues/6823
|
|
52
|
+
|
|
53
|
+
This is particularly common in Cartography when:
|
|
54
|
+
- Multiple providers sync concurrently (e.g., AWS + GCP + Okta)
|
|
55
|
+
- Large batch sizes (10,000 nodes per transaction) are used
|
|
56
|
+
- Page cache evictions occur under memory pressure (especially in Aura)
|
|
57
|
+
- Concurrent MERGE and DETACH DELETE operations overlap
|
|
58
|
+
|
|
59
|
+
:param exc: The exception to check
|
|
60
|
+
:return: True if this is a retryable ClientError (EntityNotFound), False otherwise
|
|
61
|
+
"""
|
|
62
|
+
if not isinstance(exc, neo4j.exceptions.ClientError):
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
# Only retry EntityNotFound errors - all other ClientErrors are permanent failures
|
|
66
|
+
# Note: exc.code can be None for locally-created errors (per neo4j driver docs)
|
|
67
|
+
code = exc.code
|
|
68
|
+
if code is None:
|
|
69
|
+
return False
|
|
70
|
+
return code == "Neo.ClientError.Statement.EntityNotFound"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _entity_not_found_backoff_handler(details: Dict) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Custom backoff handler that provides enhanced logging for EntityNotFound retries.
|
|
76
|
+
|
|
77
|
+
This handler logs additional context when retrying EntityNotFound errors to help
|
|
78
|
+
diagnose concurrent write issues and page cache pressure in Neo4j.
|
|
79
|
+
|
|
80
|
+
:param details: Backoff details dict containing 'exception', 'wait', 'tries', 'target'
|
|
81
|
+
"""
|
|
82
|
+
exc = details.get("exception")
|
|
83
|
+
if isinstance(exc, Exception) and _is_retryable_client_error(exc):
|
|
84
|
+
wait = details.get("wait")
|
|
85
|
+
wait_str = f"{wait:0.1f}" if wait is not None else "unknown"
|
|
86
|
+
tries = details.get("tries", 0)
|
|
87
|
+
|
|
88
|
+
if tries == 1:
|
|
89
|
+
log_msg = (
|
|
90
|
+
f"Encountered EntityNotFound error (attempt 1/{_MAX_ENTITY_NOT_FOUND_RETRIES}). "
|
|
91
|
+
f"This is expected during concurrent write operations. "
|
|
92
|
+
f"Retrying after {wait_str} seconds backoff. "
|
|
93
|
+
f"Function: {details.get('target')}. Error: {details.get('exception')}"
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
log_msg = (
|
|
97
|
+
f"EntityNotFound retry {tries}/{_MAX_ENTITY_NOT_FOUND_RETRIES}. "
|
|
98
|
+
f"Backing off {wait_str} seconds before next attempt. "
|
|
99
|
+
f"Function: {details.get('target')}. Error: {details.get('exception')}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
logger.warning(log_msg)
|
|
103
|
+
else:
|
|
104
|
+
# Fall back to standard backoff handler for other errors
|
|
105
|
+
backoff_handler(details)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _run_with_retry(operation: Callable[[], T], target: str) -> T:
|
|
109
|
+
"""
|
|
110
|
+
Execute the supplied callable with retry logic for transient network errors and
|
|
111
|
+
EntityNotFound ClientErrors.
|
|
112
|
+
"""
|
|
113
|
+
network_attempts = 0
|
|
114
|
+
entity_attempts = 0
|
|
115
|
+
network_wait = backoff.expo()
|
|
116
|
+
entity_wait = backoff.expo()
|
|
117
|
+
|
|
118
|
+
while True:
|
|
119
|
+
try:
|
|
120
|
+
result = operation()
|
|
121
|
+
# Log success if we recovered from errors
|
|
122
|
+
if network_attempts > 0:
|
|
123
|
+
logger.info(
|
|
124
|
+
f"Successfully recovered from network error after {network_attempts} "
|
|
125
|
+
f"{'retry' if network_attempts == 1 else 'retries'}. Function: {target}"
|
|
126
|
+
)
|
|
127
|
+
if entity_attempts > 0:
|
|
128
|
+
logger.info(
|
|
129
|
+
f"Successfully recovered from EntityNotFound error after {entity_attempts} "
|
|
130
|
+
f"{'retry' if entity_attempts == 1 else 'retries'}. Function: {target}"
|
|
131
|
+
)
|
|
132
|
+
return result
|
|
133
|
+
except _NETWORK_EXCEPTIONS as exc:
|
|
134
|
+
if network_attempts >= _MAX_NETWORK_RETRIES - 1:
|
|
135
|
+
raise
|
|
136
|
+
network_attempts += 1
|
|
137
|
+
wait = next(network_wait)
|
|
138
|
+
if wait is None:
|
|
139
|
+
logger.error(
|
|
140
|
+
f"Unexpected: backoff generator returned None for wait time. "
|
|
141
|
+
f"target={target}, attempts={network_attempts}, exc={exc}"
|
|
142
|
+
)
|
|
143
|
+
wait = 1.0 # Fallback to 1 second wait
|
|
144
|
+
backoff_handler(
|
|
145
|
+
{
|
|
146
|
+
"exception": exc,
|
|
147
|
+
"target": target,
|
|
148
|
+
"tries": network_attempts,
|
|
149
|
+
"wait": wait,
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
time.sleep(wait)
|
|
153
|
+
continue
|
|
154
|
+
except neo4j.exceptions.ClientError as exc:
|
|
155
|
+
if not _is_retryable_client_error(exc):
|
|
156
|
+
raise
|
|
157
|
+
if entity_attempts >= _MAX_ENTITY_NOT_FOUND_RETRIES - 1:
|
|
158
|
+
raise
|
|
159
|
+
entity_attempts += 1
|
|
160
|
+
wait = next(entity_wait)
|
|
161
|
+
if wait is None:
|
|
162
|
+
logger.error(
|
|
163
|
+
f"Unexpected: backoff generator returned None for wait time. "
|
|
164
|
+
f"target={target}, attempts={entity_attempts}, exc={exc}"
|
|
165
|
+
)
|
|
166
|
+
wait = 1.0 # Fallback to 1 second wait
|
|
167
|
+
_entity_not_found_backoff_handler(
|
|
168
|
+
{
|
|
169
|
+
"exception": exc,
|
|
170
|
+
"target": target,
|
|
171
|
+
"tries": entity_attempts,
|
|
172
|
+
"wait": wait,
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
time.sleep(wait)
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@backoff.on_exception( # type: ignore
|
|
180
|
+
backoff.expo,
|
|
181
|
+
(
|
|
182
|
+
ConnectionResetError,
|
|
183
|
+
neo4j.exceptions.ServiceUnavailable,
|
|
184
|
+
neo4j.exceptions.SessionExpired,
|
|
185
|
+
neo4j.exceptions.TransientError,
|
|
186
|
+
),
|
|
187
|
+
max_tries=5,
|
|
188
|
+
on_backoff=backoff_handler,
|
|
189
|
+
)
|
|
190
|
+
def _run_index_query_with_retry(neo4j_session: neo4j.Session, query: str) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Execute an index creation query with retry logic.
|
|
193
|
+
Index creation requires autocommit transactions and can experience transient errors.
|
|
194
|
+
|
|
195
|
+
Handles the EquivalentSchemaRuleAlreadyExists error that can occur when multiple
|
|
196
|
+
parallel sync operations attempt to create the same index simultaneously. Even though
|
|
197
|
+
we use CREATE INDEX IF NOT EXISTS, Neo4j has a race condition where concurrent
|
|
198
|
+
index creation can fail if another session creates the index between the existence
|
|
199
|
+
check and the actual creation.
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
neo4j_session.run(query)
|
|
203
|
+
except neo4j.exceptions.ClientError as e:
|
|
204
|
+
# EquivalentSchemaRuleAlreadyExists means another parallel sync already created
|
|
205
|
+
# this index, which is the desired end state. Safe to ignore.
|
|
206
|
+
if e.code == "Neo.ClientError.Schema.EquivalentSchemaRuleAlreadyExists":
|
|
207
|
+
logger.debug(
|
|
208
|
+
f"Index already exists (likely created by parallel sync): {query}"
|
|
209
|
+
)
|
|
210
|
+
return
|
|
211
|
+
raise
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def execute_write_with_retry(
|
|
215
|
+
neo4j_session: neo4j.Session,
|
|
216
|
+
tx_func: Any,
|
|
217
|
+
*args: Any,
|
|
218
|
+
**kwargs: Any,
|
|
219
|
+
) -> Any:
|
|
220
|
+
"""
|
|
221
|
+
Execute a custom transaction function with retry logic for transient errors.
|
|
222
|
+
|
|
223
|
+
This is a generic wrapper for any custom transaction function that needs retry logic
|
|
224
|
+
for EntityNotFound and other transient errors. Use this when you have complex
|
|
225
|
+
transaction logic that doesn't fit the standard load_graph_data pattern.
|
|
226
|
+
|
|
227
|
+
Example usage:
|
|
228
|
+
def my_custom_tx(tx, data_list, update_tag):
|
|
229
|
+
for item in data_list:
|
|
230
|
+
tx.run(query, **item).consume()
|
|
231
|
+
|
|
232
|
+
execute_write_with_retry(
|
|
233
|
+
neo4j_session,
|
|
234
|
+
my_custom_tx,
|
|
235
|
+
data_list,
|
|
236
|
+
update_tag
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
:param neo4j_session: The Neo4j session
|
|
240
|
+
:param tx_func: The transaction function to execute (takes neo4j.Transaction as first arg)
|
|
241
|
+
:param args: Positional arguments to pass to tx_func
|
|
242
|
+
:param kwargs: Keyword arguments to pass to tx_func
|
|
243
|
+
:return: The return value of tx_func
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
target = getattr(tx_func, "__qualname__", repr(tx_func))
|
|
247
|
+
operation = partial(neo4j_session.execute_write, tx_func, *args, **kwargs)
|
|
248
|
+
return _run_with_retry(operation, target)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def run_write_query(
|
|
252
|
+
neo4j_session: neo4j.Session, query: str, **parameters: Any
|
|
253
|
+
) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Execute a write query inside a managed transaction with retry logic.
|
|
256
|
+
|
|
257
|
+
This function now includes retry logic for:
|
|
258
|
+
- Network errors (ConnectionResetError)
|
|
259
|
+
- Service unavailability (ServiceUnavailable, SessionExpired)
|
|
260
|
+
- Transient database errors (TransientError)
|
|
261
|
+
- EntityNotFound errors during concurrent operations (specific ClientError)
|
|
262
|
+
|
|
263
|
+
Used by intel modules that run manual transactions (e.g., GCP firewalls, AWS resources).
|
|
264
|
+
|
|
265
|
+
:param neo4j_session: The Neo4j session
|
|
266
|
+
:param query: The Cypher query to execute
|
|
267
|
+
:param parameters: Parameters to pass to the query
|
|
268
|
+
:return: None
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
def _run_query_tx(tx: neo4j.Transaction) -> None:
|
|
272
|
+
tx.run(query, **parameters).consume()
|
|
273
|
+
|
|
274
|
+
def _operation() -> None:
|
|
275
|
+
neo4j_session.execute_write(_run_query_tx)
|
|
276
|
+
|
|
277
|
+
_run_with_retry(_operation, _run_query_tx.__qualname__)
|
|
278
|
+
|
|
15
279
|
|
|
16
280
|
def read_list_of_values_tx(
|
|
17
281
|
tx: neo4j.Transaction,
|
|
@@ -25,7 +289,7 @@ def read_list_of_values_tx(
|
|
|
25
289
|
Example usage:
|
|
26
290
|
query = "MATCH (a:TestNode) RETURN a.name ORDER BY a.name"
|
|
27
291
|
|
|
28
|
-
values = neo4j_session.
|
|
292
|
+
values = neo4j_session.execute_read(read_list_of_values_tx, query)
|
|
29
293
|
|
|
30
294
|
:param tx: A neo4j read transaction object
|
|
31
295
|
:param query: A neo4j query string that returns a list of single values. For example,
|
|
@@ -184,7 +448,7 @@ def write_list_of_dicts_tx(
|
|
|
184
448
|
neo4j_driver = neo4j.driver(... args ...)
|
|
185
449
|
neo4j_session = neo4j_driver.Session(... args ...)
|
|
186
450
|
|
|
187
|
-
neo4j_session.
|
|
451
|
+
neo4j_session.execute_write(
|
|
188
452
|
write_list_of_dicts_tx,
|
|
189
453
|
'''
|
|
190
454
|
UNWIND $DictList as data
|
|
@@ -204,26 +468,43 @@ def write_list_of_dicts_tx(
|
|
|
204
468
|
:param kwargs: Keyword args to be supplied to the Neo4j query.
|
|
205
469
|
:return: None
|
|
206
470
|
"""
|
|
207
|
-
tx.run(query, kwargs)
|
|
471
|
+
tx.run(query, kwargs).consume()
|
|
208
472
|
|
|
209
473
|
|
|
210
474
|
def load_graph_data(
|
|
211
475
|
neo4j_session: neo4j.Session,
|
|
212
476
|
query: str,
|
|
213
477
|
dict_list: List[Dict[str, Any]],
|
|
478
|
+
batch_size: int = 10000,
|
|
214
479
|
**kwargs,
|
|
215
480
|
) -> None:
|
|
216
481
|
"""
|
|
217
|
-
Writes data to the graph.
|
|
482
|
+
Writes data to the graph with retry logic for transient errors.
|
|
483
|
+
|
|
484
|
+
This function handles retries for:
|
|
485
|
+
- Network errors (ConnectionResetError)
|
|
486
|
+
- Service unavailability (ServiceUnavailable, SessionExpired)
|
|
487
|
+
- Transient database errors (TransientError)
|
|
488
|
+
- EntityNotFound errors during concurrent operations (ClientError with specific code)
|
|
489
|
+
|
|
490
|
+
EntityNotFound errors are retried because they commonly occur during concurrent
|
|
491
|
+
write operations when multiple threads access the same node space. This is expected
|
|
492
|
+
behavior in Neo4j's query execution pipeline, not a permanent failure.
|
|
493
|
+
|
|
218
494
|
:param neo4j_session: The Neo4j session
|
|
219
495
|
:param query: The Neo4j write query to run. This query is not meant to be handwritten, rather it should be generated
|
|
220
496
|
with cartography.graph.querybuilder.build_ingestion_query().
|
|
221
497
|
:param dict_list: The data to load to the graph represented as a list of dicts.
|
|
498
|
+
:param batch_size: The number of items to process per transaction. Defaults to 10000.
|
|
222
499
|
:param kwargs: Allows additional keyword args to be supplied to the Neo4j query.
|
|
223
500
|
:return: None
|
|
224
501
|
"""
|
|
225
|
-
|
|
226
|
-
|
|
502
|
+
if batch_size <= 0:
|
|
503
|
+
raise ValueError(f"batch_size must be greater than 0, got {batch_size}")
|
|
504
|
+
|
|
505
|
+
for data_batch in batch(dict_list, size=batch_size):
|
|
506
|
+
execute_write_with_retry(
|
|
507
|
+
neo4j_session,
|
|
227
508
|
write_list_of_dicts_tx,
|
|
228
509
|
query,
|
|
229
510
|
DictList=data_batch,
|
|
@@ -252,13 +533,33 @@ def ensure_indexes(
|
|
|
252
533
|
raise ValueError(
|
|
253
534
|
'Query provided to `ensure_indexes()` does not start with "CREATE INDEX IF NOT EXISTS".',
|
|
254
535
|
)
|
|
255
|
-
neo4j_session
|
|
536
|
+
_run_index_query_with_retry(neo4j_session, query)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def ensure_indexes_for_matchlinks(
|
|
540
|
+
neo4j_session: neo4j.Session,
|
|
541
|
+
rel_schema: CartographyRelSchema,
|
|
542
|
+
) -> None:
|
|
543
|
+
"""
|
|
544
|
+
Creates indexes for node fields if they don't exist for the given CartographyRelSchema object.
|
|
545
|
+
This is only used for load_rels() where we match on and connect existing nodes.
|
|
546
|
+
This is not used for CartographyNodeSchema objects.
|
|
547
|
+
"""
|
|
548
|
+
queries = build_create_index_queries_for_matchlink(rel_schema)
|
|
549
|
+
logger.debug(f"CREATE INDEX queries for {rel_schema.rel_label}: {queries}")
|
|
550
|
+
for query in queries:
|
|
551
|
+
if not query.startswith("CREATE INDEX IF NOT EXISTS"):
|
|
552
|
+
raise ValueError(
|
|
553
|
+
'Query provided to `ensure_indexes_for_matchlinks()` does not start with "CREATE INDEX IF NOT EXISTS".',
|
|
554
|
+
)
|
|
555
|
+
_run_index_query_with_retry(neo4j_session, query)
|
|
256
556
|
|
|
257
557
|
|
|
258
558
|
def load(
|
|
259
559
|
neo4j_session: neo4j.Session,
|
|
260
560
|
node_schema: CartographyNodeSchema,
|
|
261
561
|
dict_list: List[Dict[str, Any]],
|
|
562
|
+
batch_size: int = 10000,
|
|
262
563
|
**kwargs,
|
|
263
564
|
) -> None:
|
|
264
565
|
"""
|
|
@@ -267,12 +568,60 @@ def load(
|
|
|
267
568
|
:param neo4j_session: The Neo4j session
|
|
268
569
|
:param node_schema: The CartographyNodeSchema object to create indexes for and generate a query.
|
|
269
570
|
:param dict_list: The data to load to the graph represented as a list of dicts.
|
|
571
|
+
:param batch_size: The number of items to process per transaction. Defaults to 10000.
|
|
270
572
|
:param kwargs: Allows additional keyword args to be supplied to the Neo4j query.
|
|
271
573
|
:return: None
|
|
272
574
|
"""
|
|
575
|
+
if batch_size <= 0:
|
|
576
|
+
raise ValueError(f"batch_size must be greater than 0, got {batch_size}")
|
|
273
577
|
if len(dict_list) == 0:
|
|
274
578
|
# If there is no data to load, save some time.
|
|
275
579
|
return
|
|
276
580
|
ensure_indexes(neo4j_session, node_schema)
|
|
277
581
|
ingestion_query = build_ingestion_query(node_schema)
|
|
278
|
-
load_graph_data(
|
|
582
|
+
load_graph_data(
|
|
583
|
+
neo4j_session, ingestion_query, dict_list, batch_size=batch_size, **kwargs
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def load_matchlinks(
|
|
588
|
+
neo4j_session: neo4j.Session,
|
|
589
|
+
rel_schema: CartographyRelSchema,
|
|
590
|
+
dict_list: list[dict[str, Any]],
|
|
591
|
+
batch_size: int = 10000,
|
|
592
|
+
**kwargs,
|
|
593
|
+
) -> None:
|
|
594
|
+
"""
|
|
595
|
+
Main entrypoint for intel modules to write relationships to the graph between two existing nodes.
|
|
596
|
+
:param neo4j_session: The Neo4j session
|
|
597
|
+
:param rel_schema: The CartographyRelSchema object to generate a query.
|
|
598
|
+
:param dict_list: The data to load to the graph represented as a list of dicts. The dicts must contain the source and
|
|
599
|
+
target node ids.
|
|
600
|
+
:param batch_size: The number of items to process per transaction. Defaults to 10000.
|
|
601
|
+
:param kwargs: Allows additional keyword args to be supplied to the Neo4j query.
|
|
602
|
+
:return: None
|
|
603
|
+
"""
|
|
604
|
+
if batch_size <= 0:
|
|
605
|
+
raise ValueError(f"batch_size must be greater than 0, got {batch_size}")
|
|
606
|
+
if len(dict_list) == 0:
|
|
607
|
+
# If there is no data to load, save some time.
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
# Validate that required kwargs are provided for cleanup queries
|
|
611
|
+
if "_sub_resource_label" not in kwargs:
|
|
612
|
+
raise ValueError(
|
|
613
|
+
f"Required kwarg '_sub_resource_label' not provided for {rel_schema.rel_label}. "
|
|
614
|
+
"This is needed for cleanup queries."
|
|
615
|
+
)
|
|
616
|
+
if "_sub_resource_id" not in kwargs:
|
|
617
|
+
raise ValueError(
|
|
618
|
+
f"Required kwarg '_sub_resource_id' not provided for {rel_schema.rel_label}. "
|
|
619
|
+
"This is needed for cleanup queries."
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
ensure_indexes_for_matchlinks(neo4j_session, rel_schema)
|
|
623
|
+
matchlink_query = build_matchlink_query(rel_schema)
|
|
624
|
+
logger.debug(f"Matchlink query: {matchlink_query}")
|
|
625
|
+
load_graph_data(
|
|
626
|
+
neo4j_session, matchlink_query, dict_list, batch_size=batch_size, **kwargs
|
|
627
|
+
)
|