cartography 0.93.0rc1__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/__main__.py +1 -2
- cartography/_version.py +34 -0
- cartography/cli.py +903 -225
- cartography/client/aws/__init__.py +19 -0
- cartography/client/aws/ecr.py +51 -0
- cartography/client/core/tx.py +400 -27
- cartography/config.py +215 -10
- cartography/data/azure_permission_relationships.yaml +20 -0
- cartography/data/gcp_permission_relationships.yaml +21 -0
- cartography/data/indexes.cypher +1 -200
- cartography/data/jobs/analysis/aws_ec2_asset_exposure.json +17 -2
- cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
- cartography/data/jobs/analysis/gcp_compute_asset_inet_exposure.json +1 -1
- cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
- cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json +0 -5
- cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
- cartography/data/jobs/cleanup/github_repos_cleanup.json +27 -0
- cartography/data/jobs/scoped_analysis/aws_ec2_iaminstanceprofile.json +15 -0
- cartography/data/jobs/scoped_analysis/semgrep_sca_risk_analysis.json +13 -13
- cartography/driftdetect/__main__.py +1 -2
- cartography/driftdetect/add_shortcut.py +10 -2
- cartography/driftdetect/cli.py +72 -75
- cartography/driftdetect/detect_deviations.py +7 -3
- cartography/driftdetect/get_states.py +20 -8
- cartography/driftdetect/model.py +5 -5
- cartography/driftdetect/serializers.py +8 -6
- cartography/driftdetect/storage.py +2 -2
- cartography/graph/cleanupbuilder.py +255 -35
- cartography/graph/job.py +104 -20
- cartography/graph/querybuilder.py +689 -91
- cartography/graph/statement.py +49 -36
- 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/analysis.py +4 -1
- cartography/intel/anthropic/__init__.py +62 -0
- cartography/intel/anthropic/apikeys.py +72 -0
- cartography/intel/anthropic/users.py +75 -0
- cartography/intel/anthropic/util.py +51 -0
- cartography/intel/anthropic/workspaces.py +95 -0
- cartography/intel/aws/__init__.py +137 -59
- cartography/intel/aws/acm.py +124 -0
- cartography/intel/aws/apigateway.py +482 -217
- cartography/intel/aws/apigatewayv2.py +116 -0
- cartography/intel/aws/cloudtrail.py +105 -0
- cartography/intel/aws/cloudtrail_management_events.py +962 -0
- cartography/intel/aws/cloudwatch.py +239 -0
- cartography/intel/aws/codebuild.py +132 -0
- cartography/intel/aws/cognito.py +201 -0
- cartography/intel/aws/config.py +63 -23
- cartography/intel/aws/dynamodb.py +108 -40
- cartography/intel/aws/ec2/__init__.py +2 -2
- cartography/intel/aws/ec2/auto_scaling_groups.py +254 -189
- cartography/intel/aws/ec2/elastic_ip_addresses.py +44 -14
- cartography/intel/aws/ec2/images.py +74 -39
- cartography/intel/aws/ec2/instances.py +262 -137
- cartography/intel/aws/ec2/internet_gateways.py +44 -13
- cartography/intel/aws/ec2/key_pairs.py +72 -39
- cartography/intel/aws/ec2/launch_templates.py +143 -66
- cartography/intel/aws/ec2/load_balancer_v2s.py +119 -45
- cartography/intel/aws/ec2/load_balancers.py +165 -147
- cartography/intel/aws/ec2/network_acls.py +233 -0
- cartography/intel/aws/ec2/network_interfaces.py +150 -87
- cartography/intel/aws/ec2/reserved_instances.py +48 -17
- cartography/intel/aws/ec2/route_tables.py +327 -0
- cartography/intel/aws/ec2/security_groups.py +189 -121
- cartography/intel/aws/ec2/snapshots.py +93 -91
- cartography/intel/aws/ec2/subnets.py +70 -58
- cartography/intel/aws/ec2/tgw.py +111 -39
- cartography/intel/aws/ec2/util.py +1 -1
- cartography/intel/aws/ec2/volumes.py +69 -41
- cartography/intel/aws/ec2/vpc.py +157 -116
- cartography/intel/aws/ec2/vpc_peerings.py +317 -121
- cartography/intel/aws/ecr.py +336 -93
- cartography/intel/aws/ecr_image_layers.py +923 -0
- cartography/intel/aws/ecs.py +310 -403
- cartography/intel/aws/efs.py +261 -0
- cartography/intel/aws/eks.py +55 -29
- cartography/intel/aws/elasticache.py +130 -83
- cartography/intel/aws/elasticsearch.py +70 -24
- cartography/intel/aws/emr.py +61 -23
- 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 +978 -464
- cartography/intel/aws/iam_instance_profiles.py +73 -0
- cartography/intel/aws/identitycenter.py +847 -0
- cartography/intel/aws/inspector.py +330 -133
- cartography/intel/aws/kms.py +235 -209
- cartography/intel/aws/lambda_function.py +328 -176
- cartography/intel/aws/organizations.py +40 -19
- cartography/intel/aws/permission_relationships.py +144 -68
- cartography/intel/aws/rds.py +467 -412
- cartography/intel/aws/redshift.py +116 -50
- cartography/intel/aws/resourcegroupstaggingapi.py +198 -82
- cartography/intel/aws/resources.py +80 -42
- cartography/intel/aws/route53.py +419 -318
- cartography/intel/aws/s3.py +489 -96
- cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
- cartography/intel/aws/secretsmanager.py +217 -40
- cartography/intel/aws/securityhub.py +23 -10
- cartography/intel/aws/sns.py +226 -0
- cartography/intel/aws/sqs.py +74 -96
- cartography/intel/aws/ssm.py +142 -33
- cartography/intel/aws/util/arns.py +7 -7
- cartography/intel/aws/util/common.py +31 -4
- cartography/intel/azure/__init__.py +259 -46
- cartography/intel/azure/aks.py +175 -0
- cartography/intel/azure/app_service.py +105 -0
- cartography/intel/azure/compute.py +141 -120
- cartography/intel/azure/container_instances.py +95 -0
- cartography/intel/azure/cosmosdb.py +706 -519
- 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 +436 -392
- cartography/intel/azure/storage.py +467 -335
- cartography/intel/azure/subscription.py +49 -55
- cartography/intel/azure/tenant.py +46 -28
- cartography/intel/azure/util/common.py +13 -0
- cartography/intel/azure/util/credentials.py +58 -143
- cartography/intel/azure/util/tag.py +41 -0
- cartography/intel/bigfix/__init__.py +2 -2
- cartography/intel/bigfix/computers.py +93 -65
- cartography/intel/cloudflare/__init__.py +74 -0
- cartography/intel/cloudflare/accounts.py +57 -0
- cartography/intel/cloudflare/dnsrecords.py +64 -0
- cartography/intel/cloudflare/members.py +75 -0
- cartography/intel/cloudflare/roles.py +65 -0
- cartography/intel/cloudflare/zones.py +64 -0
- cartography/intel/create_indexes.py +5 -3
- cartography/intel/crowdstrike/__init__.py +26 -12
- cartography/intel/crowdstrike/endpoints.py +17 -45
- cartography/intel/crowdstrike/spotlight.py +13 -5
- cartography/intel/cve/__init__.py +91 -26
- cartography/intel/cve/feed.py +77 -56
- cartography/intel/digitalocean/__init__.py +22 -13
- cartography/intel/digitalocean/compute.py +75 -108
- cartography/intel/digitalocean/management.py +44 -80
- cartography/intel/digitalocean/platform.py +48 -43
- cartography/intel/dns.py +41 -12
- cartography/intel/duo/__init__.py +21 -16
- cartography/intel/duo/api_host.py +14 -9
- cartography/intel/duo/endpoints.py +50 -45
- cartography/intel/duo/groups.py +18 -14
- cartography/intel/duo/phones.py +37 -34
- cartography/intel/duo/tokens.py +26 -23
- cartography/intel/duo/users.py +54 -50
- cartography/intel/duo/web_authn_credentials.py +30 -25
- cartography/intel/entra/__init__.py +160 -0
- 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 +136 -0
- cartography/intel/entra/service_principals.py +217 -0
- cartography/intel/entra/users.py +259 -0
- cartography/intel/gcp/__init__.py +381 -175
- 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 +521 -325
- 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 +134 -179
- cartography/intel/gcp/gke.py +100 -107
- cartography/intel/gcp/iam.py +262 -0
- cartography/intel/gcp/permission_relationships.py +394 -0
- cartography/intel/gcp/policy_bindings.py +225 -0
- cartography/intel/gcp/storage.py +103 -158
- cartography/intel/github/__init__.py +66 -27
- cartography/intel/github/commits.py +423 -0
- cartography/intel/github/repos.py +871 -160
- cartography/intel/github/teams.py +386 -53
- cartography/intel/github/users.py +214 -49
- cartography/intel/github/util.py +50 -35
- 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 +101 -42
- cartography/intel/gsuite/groups.py +291 -0
- cartography/intel/gsuite/users.py +142 -0
- cartography/intel/jamf/__init__.py +19 -1
- cartography/intel/jamf/computers.py +37 -8
- cartography/intel/jamf/util.py +7 -2
- cartography/intel/kandji/__init__.py +6 -3
- cartography/intel/kandji/devices.py +40 -10
- 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 +60 -55
- cartography/intel/kubernetes/pods.py +171 -75
- cartography/intel/kubernetes/rbac.py +597 -0
- cartography/intel/kubernetes/secrets.py +95 -45
- cartography/intel/kubernetes/services.py +131 -63
- cartography/intel/kubernetes/util.py +142 -14
- cartography/intel/lastpass/__init__.py +2 -2
- cartography/intel/lastpass/users.py +23 -12
- cartography/intel/oci/__init__.py +44 -11
- cartography/intel/oci/iam.py +157 -47
- cartography/intel/oci/organizations.py +16 -7
- cartography/intel/oci/utils.py +71 -25
- cartography/intel/okta/__init__.py +66 -15
- cartography/intel/okta/applications.py +57 -25
- cartography/intel/okta/awssaml.py +105 -41
- cartography/intel/okta/factors.py +19 -5
- cartography/intel/okta/groups.py +61 -31
- cartography/intel/okta/organization.py +8 -2
- cartography/intel/okta/origins.py +9 -3
- cartography/intel/okta/roles.py +20 -7
- cartography/intel/okta/users.py +31 -10
- cartography/intel/okta/utils.py +6 -4
- 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/openai/__init__.py +86 -0
- cartography/intel/openai/adminapikeys.py +89 -0
- cartography/intel/openai/apikeys.py +96 -0
- cartography/intel/openai/projects.py +97 -0
- cartography/intel/openai/serviceaccounts.py +82 -0
- cartography/intel/openai/users.py +75 -0
- cartography/intel/openai/util.py +45 -0
- cartography/intel/pagerduty/__init__.py +8 -7
- cartography/intel/pagerduty/escalation_policies.py +31 -12
- cartography/intel/pagerduty/schedules.py +21 -8
- cartography/intel/pagerduty/services.py +18 -7
- cartography/intel/pagerduty/teams.py +13 -5
- cartography/intel/pagerduty/users.py +6 -2
- cartography/intel/pagerduty/vendors.py +6 -2
- 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/semgrep/__init__.py +30 -5
- cartography/intel/semgrep/dependencies.py +255 -0
- cartography/intel/semgrep/deployment.py +69 -0
- cartography/intel/semgrep/findings.py +157 -117
- 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/snipeit/__init__.py +44 -0
- cartography/intel/snipeit/asset.py +80 -0
- cartography/intel/snipeit/user.py +78 -0
- cartography/intel/snipeit/util.py +40 -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/tailscale/__init__.py +77 -0
- cartography/intel/tailscale/acls.py +146 -0
- cartography/intel/tailscale/devices.py +127 -0
- cartography/intel/tailscale/postureintegrations.py +81 -0
- cartography/intel/tailscale/tailnets.py +76 -0
- cartography/intel/tailscale/users.py +80 -0
- cartography/intel/tailscale/utils.py +132 -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/__init__.py +0 -0
- cartography/models/anthropic/apikey.py +94 -0
- cartography/models/anthropic/organization.py +19 -0
- cartography/models/anthropic/user.py +52 -0
- cartography/models/anthropic/workspace.py +90 -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/apigateway.py +51 -0
- cartography/models/aws/apigateway/apigatewaycertificate.py +72 -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/apigateway/apigatewayresource.py +70 -0
- cartography/models/aws/apigateway/apigatewaystage.py +75 -0
- cartography/models/aws/apigatewayv2/__init__.py +0 -0
- cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
- cartography/models/aws/cloudtrail/__init__.py +0 -0
- cartography/models/aws/cloudtrail/management_events.py +153 -0
- cartography/models/aws/cloudtrail/trail.py +106 -0
- cartography/models/aws/cloudwatch/__init__.py +0 -0
- cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
- cartography/models/aws/cloudwatch/loggroup.py +52 -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/gsi.py +30 -22
- cartography/models/aws/dynamodb/tables.py +27 -17
- cartography/models/aws/ec2/auto_scaling_groups.py +224 -0
- cartography/models/aws/ec2/images.py +36 -34
- cartography/models/aws/ec2/instances.py +85 -38
- cartography/models/aws/ec2/keypair.py +59 -0
- cartography/models/aws/ec2/keypair_instance.py +76 -0
- cartography/models/aws/ec2/launch_configurations.py +59 -0
- cartography/models/aws/ec2/launch_template_versions.py +48 -38
- cartography/models/aws/ec2/launch_templates.py +21 -17
- cartography/models/aws/ec2/load_balancer_listeners.py +72 -0
- cartography/models/aws/ec2/load_balancers.py +112 -0
- cartography/models/aws/ec2/network_acl_rules.py +106 -0
- cartography/models/aws/ec2/network_acls.py +95 -0
- cartography/models/aws/ec2/networkinterface_instance.py +52 -39
- cartography/models/aws/ec2/networkinterfaces.py +57 -37
- cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
- cartography/models/aws/ec2/reservations.py +18 -14
- cartography/models/aws/ec2/route_table_associations.py +97 -0
- cartography/models/aws/ec2/route_tables.py +128 -0
- cartography/models/aws/ec2/routes.py +85 -0
- cartography/models/aws/ec2/security_group_rules.py +109 -0
- cartography/models/aws/ec2/security_groups.py +90 -0
- cartography/models/aws/ec2/securitygroup_instance.py +29 -20
- cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
- cartography/models/aws/ec2/snapshots.py +58 -0
- cartography/models/aws/ec2/subnet_instance.py +26 -19
- cartography/models/aws/ec2/subnet_networkinterface.py +42 -31
- cartography/models/aws/ec2/subnets.py +65 -0
- cartography/models/aws/ec2/volumes.py +67 -40
- 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/__init__.py +0 -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 +79 -0
- cartography/models/aws/eks/clusters.py +23 -21
- 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/emr.py +32 -30
- 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/__init__.py +0 -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/instanceprofile.py +76 -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/__init__.py +0 -0
- cartography/models/aws/identitycenter/awsidentitycenter.py +49 -0
- cartography/models/aws/identitycenter/awspermissionset.py +162 -0
- cartography/models/aws/identitycenter/awssogroup.py +70 -0
- cartography/models/aws/identitycenter/awsssouser.py +110 -0
- cartography/models/aws/inspector/findings.py +124 -58
- cartography/models/aws/inspector/packages.py +18 -42
- 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/__init__.py +0 -0
- cartography/models/aws/s3/account_public_access_block.py +51 -0
- cartography/models/aws/s3/notification.py +24 -0
- cartography/models/aws/secretsmanager/__init__.py +0 -0
- cartography/models/aws/secretsmanager/secret.py +106 -0
- cartography/models/aws/secretsmanager/secret_version.py +114 -0
- cartography/models/aws/sns/__init__.py +0 -0
- cartography/models/aws/sns/topic.py +50 -0
- 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/aws/ssm/instance_information.py +51 -39
- cartography/models/aws/ssm/instance_patch.py +32 -26
- cartography/models/aws/ssm/parameters.py +84 -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 +42 -38
- cartography/models/bigfix/bigfix_root.py +3 -3
- cartography/models/cloudflare/__init__.py +0 -0
- cartography/models/cloudflare/account.py +25 -0
- cartography/models/cloudflare/dnsrecord.py +55 -0
- cartography/models/cloudflare/member.py +86 -0
- cartography/models/cloudflare/role.py +44 -0
- cartography/models/cloudflare/zone.py +59 -0
- cartography/models/core/common.py +53 -2
- cartography/models/core/nodes.py +20 -4
- cartography/models/core/relationships.py +58 -6
- cartography/models/crowdstrike/__init__.py +0 -0
- cartography/models/crowdstrike/hosts.py +51 -0
- cartography/models/cve/cve.py +34 -32
- cartography/models/cve/cve_feed.py +6 -6
- cartography/models/digitalocean/__init__.py +0 -0
- cartography/models/digitalocean/account.py +21 -0
- cartography/models/digitalocean/droplet.py +58 -0
- cartography/models/digitalocean/project.py +48 -0
- cartography/models/duo/api_host.py +3 -3
- cartography/models/duo/endpoint.py +43 -41
- cartography/models/duo/group.py +14 -14
- cartography/models/duo/phone.py +27 -27
- cartography/models/duo/token.py +16 -16
- cartography/models/duo/user.py +50 -44
- cartography/models/duo/web_authn_credential.py +27 -19
- cartography/models/entra/__init__.py +0 -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/ou.py +48 -0
- cartography/models/entra/service_principal.py +104 -0
- cartography/models/entra/tenant.py +39 -0
- cartography/models/entra/user.py +90 -0
- 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 +73 -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/orgs.py +27 -0
- cartography/models/github/teams.py +74 -22
- cartography/models/github/users.py +149 -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 +22 -17
- cartography/models/kandji/tenant.py +6 -4
- 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/tenant.py +3 -3
- cartography/models/lastpass/user.py +36 -28
- 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/__init__.py +0 -0
- cartography/models/openai/adminapikey.py +94 -0
- cartography/models/openai/apikey.py +88 -0
- cartography/models/openai/organization.py +17 -0
- cartography/models/openai/project.py +89 -0
- cartography/models/openai/serviceaccount.py +50 -0
- cartography/models/openai/user.py +53 -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/semgrep/dependencies.py +102 -0
- cartography/models/semgrep/deployment.py +5 -5
- cartography/models/semgrep/findings.py +58 -40
- cartography/models/semgrep/locations.py +27 -21
- 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/__init__.py +0 -0
- cartography/models/snipeit/asset.py +92 -0
- cartography/models/snipeit/tenant.py +19 -0
- cartography/models/snipeit/user.py +60 -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/__init__.py +0 -0
- cartography/models/tailscale/device.py +96 -0
- cartography/models/tailscale/group.py +86 -0
- cartography/models/tailscale/postureintegration.py +58 -0
- cartography/models/tailscale/tag.py +102 -0
- cartography/models/tailscale/tailnet.py +29 -0
- cartography/models/tailscale/user.py +57 -0
- 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/stats.py +4 -4
- cartography/sync.py +137 -31
- cartography/util.py +187 -77
- cartography-0.123.0.dist-info/METADATA +230 -0
- cartography-0.123.0.dist-info/RECORD +856 -0
- {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info}/WHEEL +1 -1
- {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info}/entry_points.txt +1 -0
- {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info/licenses}/LICENSE +1 -1
- cartography/data/jobs/analysis/aws_ec2_iaminstance.json +0 -10
- cartography/data/jobs/analysis/aws_ec2_iaminstanceprofile.json +0 -10
- cartography/data/jobs/cleanup/aws_apigateway_details.json +0 -10
- 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_apigateway_cleanup.json +0 -45
- 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_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/crxcavator_import_cleanup.json +0 -18
- 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/github_users_cleanup.json +0 -23
- 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/crxcavator/__init__.py +0 -44
- cartography/intel/crxcavator/crxcavator.py +0 -329
- cartography/intel/gcp/crm.py +0 -302
- cartography/intel/gsuite/api.py +0 -284
- cartography/models/aws/ec2/keypairs.py +0 -64
- cartography-0.93.0rc1.dist-info/METADATA +0 -55
- cartography-0.93.0rc1.dist-info/NOTICE +0 -4
- cartography-0.93.0rc1.dist-info/RECORD +0 -341
- /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
- {cartography-0.93.0rc1.dist-info → cartography-0.123.0.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import asdict
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version
|
|
3
5
|
from string import Template
|
|
4
|
-
from typing import Dict
|
|
5
|
-
from typing import List
|
|
6
|
-
from typing import Optional
|
|
7
|
-
from typing import Set
|
|
8
|
-
from typing import Tuple
|
|
9
6
|
|
|
10
7
|
from cartography.models.core.common import PropertyRef
|
|
11
8
|
from cartography.models.core.nodes import CartographyNodeProperties
|
|
@@ -14,21 +11,319 @@ from cartography.models.core.nodes import ExtraNodeLabels
|
|
|
14
11
|
from cartography.models.core.relationships import CartographyRelSchema
|
|
15
12
|
from cartography.models.core.relationships import LinkDirection
|
|
16
13
|
from cartography.models.core.relationships import OtherRelationships
|
|
14
|
+
from cartography.models.core.relationships import SourceNodeMatcher
|
|
17
15
|
from cartography.models.core.relationships import TargetNodeMatcher
|
|
16
|
+
from cartography.models.ontology.mapping import (
|
|
17
|
+
get_semantic_label_mapping_from_node_schema,
|
|
18
|
+
)
|
|
19
|
+
from cartography.models.ontology.mapping.specs import OntologyFieldMapping
|
|
18
20
|
|
|
19
21
|
logger = logging.getLogger(__name__)
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
def _build_ontology_field_statement_invert_boolean(
|
|
25
|
+
mapping_field: OntologyFieldMapping,
|
|
26
|
+
property_ref: PropertyRef,
|
|
27
|
+
) -> str:
|
|
28
|
+
# toBooleanOrNull will return a boolean or null if it can't be converted
|
|
29
|
+
# coalesce will return the first non-null value, so if toBooleanOrNull returns null,
|
|
30
|
+
# we invert the boolean value of the property_ref existence
|
|
31
|
+
# ex: "false", "0", "no" => false; anything else => true; null/absent => true
|
|
32
|
+
invert_boolean_template = Template(
|
|
33
|
+
"i.$node_property = (NOT(coalesce(toBooleanOrNull($property_ref), false)))"
|
|
34
|
+
)
|
|
35
|
+
return invert_boolean_template.safe_substitute(
|
|
36
|
+
node_property=f"_ont_{mapping_field.ontology_field}",
|
|
37
|
+
property_ref=property_ref,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _build_ontology_field_statement_to_boolean(
|
|
42
|
+
mapping_field: OntologyFieldMapping,
|
|
43
|
+
property_ref: PropertyRef,
|
|
44
|
+
) -> str:
|
|
45
|
+
# toBoleanOrNull will return a boolean or null if it can't be converted
|
|
46
|
+
# coalesce will return the first non-null value, so if toBooleanOrNull returns null,
|
|
47
|
+
# it will return whether the property_ref is not null (i.e., true if property_ref exists)
|
|
48
|
+
# this way, any non-null value is treated as true
|
|
49
|
+
# ex: "true", "1", "yes" => true; anything else => true; null/absent => false
|
|
50
|
+
to_boolean_template = Template(
|
|
51
|
+
"i.$node_property = coalesce(toBooleanOrNull($property_ref), ($property_ref IS NOT NULL))"
|
|
52
|
+
)
|
|
53
|
+
return to_boolean_template.safe_substitute(
|
|
54
|
+
node_property=f"_ont_{mapping_field.ontology_field}",
|
|
55
|
+
property_ref=property_ref,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _build_ontology_field_statement_equal_boolean(
|
|
60
|
+
mapping_field: OntologyFieldMapping,
|
|
61
|
+
property_ref: PropertyRef,
|
|
62
|
+
) -> str | None:
|
|
63
|
+
# we check if the property_ref is in the list of expected boolean values
|
|
64
|
+
equal_boolean_template = Template(
|
|
65
|
+
"i.$node_property = ($property_ref IN $property_values)"
|
|
66
|
+
)
|
|
67
|
+
extra_field_values = mapping_field.extra.get("values")
|
|
68
|
+
if extra_field_values is None:
|
|
69
|
+
# should not occure due to unit test but failing gracefully
|
|
70
|
+
logger.warning(
|
|
71
|
+
"equal_boolean special handling requires 'values' in extra for field %s",
|
|
72
|
+
mapping_field.ontology_field,
|
|
73
|
+
)
|
|
74
|
+
return None
|
|
75
|
+
if not isinstance(extra_field_values, list):
|
|
76
|
+
logger.warning(
|
|
77
|
+
"equal_boolean special handling 'values' in extra for field %s must be a list",
|
|
78
|
+
mapping_field.ontology_field,
|
|
79
|
+
)
|
|
80
|
+
return None
|
|
81
|
+
return equal_boolean_template.substitute(
|
|
82
|
+
node_property=f"_ont_{mapping_field.ontology_field}",
|
|
83
|
+
property_ref=property_ref,
|
|
84
|
+
property_values=extra_field_values,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _escape_cypher_string(value: str) -> str:
|
|
89
|
+
r"""
|
|
90
|
+
Escape special characters in a string value for use in a Cypher string literal.
|
|
91
|
+
|
|
92
|
+
In Cypher, string literals are enclosed in double quotes, and the following characters
|
|
93
|
+
must be escaped with a backslash:
|
|
94
|
+
- Backslash (\) -> \\
|
|
95
|
+
- Double quote (") -> \"
|
|
96
|
+
|
|
97
|
+
:param value: The string value to escape
|
|
98
|
+
:return: The escaped string value safe for use in a Cypher string literal
|
|
99
|
+
"""
|
|
100
|
+
# Escape backslashes first (must be done before escaping quotes)
|
|
101
|
+
escaped = value.replace("\\", "\\\\")
|
|
102
|
+
# Then escape double quotes
|
|
103
|
+
escaped = escaped.replace('"', '\\"')
|
|
104
|
+
return escaped
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _build_ontology_field_statement_static_value(
|
|
108
|
+
mapping_field: OntologyFieldMapping,
|
|
109
|
+
) -> str | None:
|
|
110
|
+
# Sets a static value for the ontology field
|
|
111
|
+
# The value is provided in extra['value']
|
|
112
|
+
static_value_template = Template("i.$node_property = $static_value")
|
|
113
|
+
extra_value = mapping_field.extra.get("value")
|
|
114
|
+
if extra_value is None:
|
|
115
|
+
# should not occur due to unit test but failing gracefully
|
|
116
|
+
logger.warning(
|
|
117
|
+
"static_value special handling requires 'value' in extra for field %s",
|
|
118
|
+
mapping_field.ontology_field,
|
|
119
|
+
)
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
# Format the value appropriately for Cypher
|
|
123
|
+
if isinstance(extra_value, str):
|
|
124
|
+
formatted_value = f'"{_escape_cypher_string(extra_value)}"'
|
|
125
|
+
elif isinstance(extra_value, bool):
|
|
126
|
+
formatted_value = str(extra_value).lower()
|
|
127
|
+
else:
|
|
128
|
+
formatted_value = str(extra_value)
|
|
129
|
+
|
|
130
|
+
return static_value_template.substitute(
|
|
131
|
+
node_property=f"_ont_{mapping_field.ontology_field}",
|
|
132
|
+
static_value=formatted_value,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _build_ontology_field_statement_or_boolean(
|
|
137
|
+
mapping_field: OntologyFieldMapping,
|
|
138
|
+
node_property_map: dict[str, PropertyRef],
|
|
139
|
+
) -> str | None:
|
|
140
|
+
# The or_clause is needed to avoid comparing nulls to boolean values
|
|
141
|
+
# See: https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/#cypher-null-logical-operators
|
|
142
|
+
or_clause = Template("coalesce(toBooleanOrNull($property_ref), false)")
|
|
143
|
+
or_boolean_template = Template("i.$node_property = ($property_condition)")
|
|
144
|
+
extra_fields = mapping_field.extra.get("fields")
|
|
145
|
+
if extra_fields is None:
|
|
146
|
+
# should not occure due to unit test but failing gracefully
|
|
147
|
+
logger.warning(
|
|
148
|
+
"or_boolean special handling requires 'fields' in extra for field %s",
|
|
149
|
+
mapping_field.ontology_field,
|
|
150
|
+
)
|
|
151
|
+
return None
|
|
152
|
+
if not isinstance(extra_fields, list):
|
|
153
|
+
# should not occure due to unit test but failing gracefully
|
|
154
|
+
logger.warning(
|
|
155
|
+
"or_boolean special handling 'fields' in extra for field %s must be a list",
|
|
156
|
+
mapping_field.ontology_field,
|
|
157
|
+
)
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
property_conditions = [
|
|
161
|
+
or_clause.substitute(
|
|
162
|
+
property_ref=node_property_map.get(mapping_field.node_field),
|
|
163
|
+
)
|
|
164
|
+
]
|
|
165
|
+
for extra_field in mapping_field.extra.get("fields", []):
|
|
166
|
+
extra_property_ref = node_property_map.get(extra_field)
|
|
167
|
+
if not extra_property_ref:
|
|
168
|
+
# should not occure due to unit test but failing gracefully
|
|
169
|
+
logger.warning(
|
|
170
|
+
"Extra field '%s' not found in node properties for or_boolean special handling of field %s",
|
|
171
|
+
extra_field,
|
|
172
|
+
mapping_field.ontology_field,
|
|
173
|
+
)
|
|
174
|
+
continue
|
|
175
|
+
property_conditions.append(
|
|
176
|
+
or_clause.substitute(
|
|
177
|
+
property_ref=extra_property_ref,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
full_property_condition = " OR ".join(property_conditions)
|
|
181
|
+
return or_boolean_template.substitute(
|
|
182
|
+
node_property=f"_ont_{mapping_field.ontology_field}",
|
|
183
|
+
property_condition=full_property_condition,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _build_ontology_field_statement_nor_boolean(
|
|
188
|
+
mapping_field: OntologyFieldMapping,
|
|
189
|
+
node_property_map: dict[str, PropertyRef],
|
|
190
|
+
) -> str | None:
|
|
191
|
+
nor_clause = Template("NOT(coalesce(toBooleanOrNull($property_ref), false))")
|
|
192
|
+
nor_boolean_template = Template("i.$node_property = ($property_condition)")
|
|
193
|
+
extra_fields = mapping_field.extra.get("fields")
|
|
194
|
+
if extra_fields is None:
|
|
195
|
+
# should not occure due to unit test but failing gracefully
|
|
196
|
+
logger.warning(
|
|
197
|
+
"nor_boolean special handling requires 'fields' in extra for field %s",
|
|
198
|
+
mapping_field.ontology_field,
|
|
199
|
+
)
|
|
200
|
+
return None
|
|
201
|
+
if not isinstance(extra_fields, list):
|
|
202
|
+
# should not occure due to unit test but failing gracefully
|
|
203
|
+
logger.warning(
|
|
204
|
+
"nor_boolean special handling 'fields' in extra for field %s must be a list",
|
|
205
|
+
mapping_field.ontology_field,
|
|
206
|
+
)
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
property_conditions = [
|
|
210
|
+
nor_clause.substitute(
|
|
211
|
+
property_ref=node_property_map.get(mapping_field.node_field),
|
|
212
|
+
)
|
|
213
|
+
]
|
|
214
|
+
for extra_field in mapping_field.extra.get("fields", []):
|
|
215
|
+
extra_property_ref = node_property_map.get(extra_field)
|
|
216
|
+
if not extra_property_ref:
|
|
217
|
+
# should not occure due to unit test but failing gracefully
|
|
218
|
+
logger.warning(
|
|
219
|
+
"Extra field '%s' not found in node properties for nor_boolean special handling of field %s",
|
|
220
|
+
extra_field,
|
|
221
|
+
mapping_field.ontology_field,
|
|
222
|
+
)
|
|
223
|
+
continue
|
|
224
|
+
property_conditions.append(
|
|
225
|
+
nor_clause.substitute(
|
|
226
|
+
property_ref=extra_property_ref,
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
full_property_condition = " AND ".join(property_conditions)
|
|
230
|
+
return nor_boolean_template.substitute(
|
|
231
|
+
node_property=f"_ont_{mapping_field.ontology_field}",
|
|
232
|
+
property_condition=full_property_condition,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _build_ontology_node_properties_statement(
|
|
237
|
+
node_schema: CartographyNodeSchema,
|
|
238
|
+
node_property_map: dict[str, PropertyRef],
|
|
239
|
+
) -> str:
|
|
240
|
+
# DOC
|
|
241
|
+
# Try to get the mapping for the given node schema
|
|
242
|
+
ontology_mapping = get_semantic_label_mapping_from_node_schema(node_schema)
|
|
243
|
+
if not ontology_mapping:
|
|
244
|
+
return ""
|
|
245
|
+
|
|
246
|
+
source = _get_module_from_schema(node_schema).rsplit(":", maxsplit=1)[-1]
|
|
247
|
+
set_clauses = [f"i._ont_source = '{source}'"]
|
|
248
|
+
for mapping_field in ontology_mapping.fields:
|
|
249
|
+
ontology_field_name = f"_ont_{mapping_field.ontology_field}"
|
|
250
|
+
node_propertyref = node_property_map.get(mapping_field.node_field)
|
|
251
|
+
|
|
252
|
+
# Handle static_value special handling first - it doesn't require a node_field
|
|
253
|
+
if mapping_field.special_handling == "static_value":
|
|
254
|
+
static_value_statement = _build_ontology_field_statement_static_value(
|
|
255
|
+
mapping_field
|
|
256
|
+
)
|
|
257
|
+
if static_value_statement:
|
|
258
|
+
set_clauses.append(static_value_statement)
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
# Skip validation for special_handling that don't require node_field
|
|
262
|
+
if not node_propertyref:
|
|
263
|
+
# This should not occure due to unit test but failing gracefully
|
|
264
|
+
logger.warning(
|
|
265
|
+
"Field '%s' not found in node properties for node schema %s",
|
|
266
|
+
mapping_field.node_field,
|
|
267
|
+
node_schema.__class__.__name__,
|
|
268
|
+
)
|
|
269
|
+
continue
|
|
270
|
+
if mapping_field.special_handling == "invert_boolean":
|
|
271
|
+
set_clauses.append(
|
|
272
|
+
_build_ontology_field_statement_invert_boolean(
|
|
273
|
+
mapping_field,
|
|
274
|
+
node_propertyref,
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
elif mapping_field.special_handling == "to_boolean":
|
|
278
|
+
set_clauses.append(
|
|
279
|
+
_build_ontology_field_statement_to_boolean(
|
|
280
|
+
mapping_field,
|
|
281
|
+
node_propertyref,
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
elif mapping_field.special_handling == "equal_boolean":
|
|
285
|
+
equal_boolean_statement = _build_ontology_field_statement_equal_boolean(
|
|
286
|
+
mapping_field,
|
|
287
|
+
node_propertyref,
|
|
288
|
+
)
|
|
289
|
+
if equal_boolean_statement:
|
|
290
|
+
set_clauses.append(equal_boolean_statement)
|
|
291
|
+
elif mapping_field.special_handling == "or_boolean":
|
|
292
|
+
or_boolean_statement = _build_ontology_field_statement_or_boolean(
|
|
293
|
+
mapping_field, node_property_map
|
|
294
|
+
)
|
|
295
|
+
if or_boolean_statement:
|
|
296
|
+
set_clauses.append(or_boolean_statement)
|
|
297
|
+
elif mapping_field.special_handling == "nor_boolean":
|
|
298
|
+
nor_boolean_statement = _build_ontology_field_statement_nor_boolean(
|
|
299
|
+
mapping_field, node_property_map
|
|
300
|
+
)
|
|
301
|
+
if nor_boolean_statement:
|
|
302
|
+
set_clauses.append(nor_boolean_statement)
|
|
303
|
+
else:
|
|
304
|
+
simple_field_template = Template("i.$node_property = $property_ref")
|
|
305
|
+
set_clauses.append(
|
|
306
|
+
simple_field_template.substitute(
|
|
307
|
+
node_property=ontology_field_name,
|
|
308
|
+
property_ref=node_propertyref,
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
if len(set_clauses) == 0:
|
|
312
|
+
return ""
|
|
313
|
+
# Add initial newline
|
|
314
|
+
return ",\n" + ",\n".join(set_clauses)
|
|
315
|
+
|
|
316
|
+
|
|
22
317
|
def _build_node_properties_statement(
|
|
23
|
-
|
|
24
|
-
|
|
318
|
+
node_property_map: dict[str, PropertyRef],
|
|
319
|
+
extra_node_labels: ExtraNodeLabels | None = None,
|
|
25
320
|
) -> str:
|
|
26
321
|
"""
|
|
27
322
|
Generate a Neo4j clause that sets node properties using the given mapping of attribute names to PropertyRefs.
|
|
28
323
|
|
|
29
324
|
As seen in this example,
|
|
30
325
|
|
|
31
|
-
node_property_map:
|
|
326
|
+
node_property_map: dict[str, PropertyRef] = {
|
|
32
327
|
'id': PropertyRef("Id"),
|
|
33
328
|
'node_prop_1': PropertyRef("Prop1"),
|
|
34
329
|
'node_prop_2': PropertyRef("Prop2", set_in_kwargs=True),
|
|
@@ -46,29 +341,38 @@ def _build_node_properties_statement(
|
|
|
46
341
|
:param extra_node_labels: Optional ExtraNodeLabels object to set on the node as string
|
|
47
342
|
:return: The resulting Neo4j SET clause to set the given attributes on the node
|
|
48
343
|
"""
|
|
49
|
-
ingest_fields_template = Template(
|
|
344
|
+
ingest_fields_template = Template("i.$node_property = $property_ref")
|
|
50
345
|
|
|
51
|
-
set_clause =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
346
|
+
set_clause = ",\n".join(
|
|
347
|
+
[
|
|
348
|
+
ingest_fields_template.safe_substitute(
|
|
349
|
+
node_property=node_property,
|
|
350
|
+
property_ref=property_ref,
|
|
351
|
+
)
|
|
352
|
+
for node_property, property_ref in node_property_map.items()
|
|
353
|
+
if node_property
|
|
354
|
+
!= "id" # The `MERGE` clause will have already set `id`; let's not set it again.
|
|
355
|
+
],
|
|
356
|
+
)
|
|
56
357
|
|
|
57
358
|
# Set extra labels on the node if specified
|
|
58
359
|
if extra_node_labels:
|
|
59
|
-
extra_labels =
|
|
360
|
+
extra_labels = ":".join([label for label in extra_node_labels.labels])
|
|
60
361
|
set_clause += f",\n i:{extra_labels}"
|
|
61
362
|
return set_clause
|
|
62
363
|
|
|
63
364
|
|
|
64
|
-
def _build_rel_properties_statement(
|
|
365
|
+
def _build_rel_properties_statement(
|
|
366
|
+
rel_var: str,
|
|
367
|
+
rel_property_map: dict[str, PropertyRef] | None = None,
|
|
368
|
+
) -> str:
|
|
65
369
|
"""
|
|
66
370
|
Generate a Neo4j clause that sets relationship properties using the given mapping of attribute names to
|
|
67
371
|
PropertyRefs.
|
|
68
372
|
|
|
69
373
|
In this code example:
|
|
70
374
|
|
|
71
|
-
rel_property_map:
|
|
375
|
+
rel_property_map: dict[str, PropertyRef] = {
|
|
72
376
|
'rel_prop_1': PropertyRef("Prop1"),
|
|
73
377
|
'rel_prop_2': PropertyRef("Prop2", static=True),
|
|
74
378
|
}
|
|
@@ -83,33 +387,41 @@ def _build_rel_properties_statement(rel_var: str, rel_property_map: Optional[Dic
|
|
|
83
387
|
:param rel_property_map: Mapping of relationship attribute names as str to PropertyRef objects
|
|
84
388
|
:return: The resulting Neo4j SET clause to set the given attributes on the relationship
|
|
85
389
|
"""
|
|
86
|
-
set_clause =
|
|
87
|
-
ingest_fields_template = Template(
|
|
390
|
+
set_clause = ""
|
|
391
|
+
ingest_fields_template = Template("$rel_var.$rel_property = $property_ref")
|
|
88
392
|
|
|
89
393
|
if rel_property_map:
|
|
90
|
-
set_clause +=
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
394
|
+
set_clause += ",\n".join(
|
|
395
|
+
[
|
|
396
|
+
ingest_fields_template.safe_substitute(
|
|
397
|
+
rel_var=rel_var,
|
|
398
|
+
rel_property=rel_property,
|
|
399
|
+
property_ref=property_ref,
|
|
400
|
+
)
|
|
401
|
+
for rel_property, property_ref in rel_property_map.items()
|
|
402
|
+
],
|
|
403
|
+
)
|
|
98
404
|
return set_clause
|
|
99
405
|
|
|
100
406
|
|
|
101
|
-
def _build_match_clause(matcher: TargetNodeMatcher) -> str:
|
|
407
|
+
def _build_match_clause(matcher: TargetNodeMatcher | SourceNodeMatcher) -> str:
|
|
102
408
|
"""
|
|
103
409
|
Generate a Neo4j match statement on one or more keys and values for a given node.
|
|
104
|
-
:param matcher: A TargetNodeMatcher object
|
|
410
|
+
:param matcher: A TargetNodeMatcher or SourceNodeMatcher object
|
|
105
411
|
:return: a Neo4j match clause
|
|
106
412
|
"""
|
|
107
413
|
match = Template("$Key: $PropRef")
|
|
108
414
|
matcher_asdict = asdict(matcher)
|
|
109
|
-
return
|
|
415
|
+
return ", ".join(
|
|
416
|
+
match.safe_substitute(Key=key, PropRef=prop_ref)
|
|
417
|
+
for key, prop_ref in matcher_asdict.items()
|
|
418
|
+
)
|
|
110
419
|
|
|
111
420
|
|
|
112
|
-
def _build_where_clause_for_rel_match(
|
|
421
|
+
def _build_where_clause_for_rel_match(
|
|
422
|
+
node_var: str,
|
|
423
|
+
matcher: TargetNodeMatcher,
|
|
424
|
+
) -> str:
|
|
113
425
|
"""
|
|
114
426
|
Same as _build_match_clause, but puts the matching logic in a WHERE clause.
|
|
115
427
|
This is intended specifically to use for joining with relationships where we need a case-insensitive match.
|
|
@@ -118,39 +430,70 @@ def _build_where_clause_for_rel_match(node_var: str, matcher: TargetNodeMatcher)
|
|
|
118
430
|
"""
|
|
119
431
|
match = Template("$node_var.$key = $prop_ref")
|
|
120
432
|
case_insensitive_match = Template("toLower($node_var.$key) = toLower($prop_ref)")
|
|
433
|
+
fuzzy_and_ignorecase_match = Template(
|
|
434
|
+
"toLower($node_var.$key) CONTAINS toLower($prop_ref)"
|
|
435
|
+
)
|
|
436
|
+
# This assumes that item.$prop_ref points to a list available on the data object
|
|
437
|
+
one_to_many_match = Template("$node_var.$key IN $prop_ref")
|
|
121
438
|
|
|
122
439
|
matcher_asdict = asdict(matcher)
|
|
123
440
|
|
|
124
441
|
result = []
|
|
125
442
|
for key, prop_ref in matcher_asdict.items():
|
|
126
443
|
if prop_ref.ignore_case:
|
|
127
|
-
prop_line = case_insensitive_match.safe_substitute(
|
|
444
|
+
prop_line = case_insensitive_match.safe_substitute(
|
|
445
|
+
node_var=node_var,
|
|
446
|
+
key=key,
|
|
447
|
+
prop_ref=prop_ref,
|
|
448
|
+
)
|
|
449
|
+
elif prop_ref.fuzzy_and_ignore_case:
|
|
450
|
+
prop_line = fuzzy_and_ignorecase_match.safe_substitute(
|
|
451
|
+
node_var=node_var, key=key, prop_ref=prop_ref
|
|
452
|
+
)
|
|
453
|
+
elif prop_ref.one_to_many:
|
|
454
|
+
# Allow a single node to be attached to multiple others at once using a list of IDs provided in kwargs
|
|
455
|
+
prop_line = one_to_many_match.safe_substitute(
|
|
456
|
+
node_var=node_var, key=key, prop_ref=prop_ref
|
|
457
|
+
)
|
|
128
458
|
else:
|
|
129
|
-
|
|
459
|
+
# Exact match (default; most efficient)
|
|
460
|
+
prop_line = match.safe_substitute(
|
|
461
|
+
node_var=node_var,
|
|
462
|
+
key=key,
|
|
463
|
+
prop_ref=prop_ref,
|
|
464
|
+
)
|
|
130
465
|
result.append(prop_line)
|
|
131
|
-
return
|
|
466
|
+
return " AND\n".join(result)
|
|
132
467
|
|
|
133
468
|
|
|
134
|
-
def _asdict_with_validate_relprops(
|
|
469
|
+
def _asdict_with_validate_relprops(
|
|
470
|
+
link: CartographyRelSchema,
|
|
471
|
+
) -> dict[str, PropertyRef]:
|
|
135
472
|
"""
|
|
136
473
|
Give a helpful error message when forgetting to put `()` when instantiating a CartographyRelSchema, as this
|
|
137
474
|
isn't always caught by IDEs.
|
|
138
475
|
"""
|
|
139
476
|
try:
|
|
140
|
-
rel_props_as_dict:
|
|
477
|
+
rel_props_as_dict: dict[str, PropertyRef] = asdict(link.properties)
|
|
141
478
|
except TypeError as e:
|
|
142
|
-
if
|
|
479
|
+
if (
|
|
480
|
+
e.args
|
|
481
|
+
and e.args[0]
|
|
482
|
+
and e.args == "asdict() should be called on dataclass instances"
|
|
483
|
+
):
|
|
143
484
|
logger.error(
|
|
144
485
|
f'TypeError thrown when trying to draw relation "{link.rel_label}" to a "{link.target_node_label}" '
|
|
145
|
-
f
|
|
146
|
-
f
|
|
147
|
-
f
|
|
486
|
+
f"node. Please make sure that you did not forget to write `()` when specifying `properties` in the"
|
|
487
|
+
f"dataclass. "
|
|
488
|
+
f"For example, do `properties: RelProp = RelProp()`; NOT `properties: RelProp = RelProp`.",
|
|
148
489
|
)
|
|
149
490
|
raise
|
|
150
491
|
return rel_props_as_dict
|
|
151
492
|
|
|
152
493
|
|
|
153
|
-
def _build_attach_sub_resource_statement(
|
|
494
|
+
def _build_attach_sub_resource_statement(
|
|
495
|
+
sub_resource_link: CartographyRelSchema | None = None,
|
|
496
|
+
) -> str:
|
|
154
497
|
"""
|
|
155
498
|
Generates a Neo4j statement to attach a sub resource to a node. A 'sub resource' is a term we made up to describe
|
|
156
499
|
billing units of a given resource. For example,
|
|
@@ -164,7 +507,7 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
|
|
|
164
507
|
keys, and directionality. If sub_resource_link is None, return an empty string.
|
|
165
508
|
"""
|
|
166
509
|
if not sub_resource_link:
|
|
167
|
-
return
|
|
510
|
+
return ""
|
|
168
511
|
|
|
169
512
|
sub_resource_attach_template = Template(
|
|
170
513
|
"""
|
|
@@ -174,6 +517,8 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
|
|
|
174
517
|
$RelMergeClause
|
|
175
518
|
ON CREATE SET r.firstseen = timestamp()
|
|
176
519
|
SET
|
|
520
|
+
r._module_name = "$module_name",
|
|
521
|
+
r._module_version = "$module_version",
|
|
177
522
|
$set_rel_properties_statement
|
|
178
523
|
""",
|
|
179
524
|
)
|
|
@@ -183,22 +528,31 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
|
|
|
183
528
|
else:
|
|
184
529
|
rel_merge_template = Template("""MERGE (i)-[r:$SubResourceRelLabel]->(j)""")
|
|
185
530
|
|
|
186
|
-
rel_merge_clause = rel_merge_template.safe_substitute(
|
|
531
|
+
rel_merge_clause = rel_merge_template.safe_substitute(
|
|
532
|
+
SubResourceRelLabel=sub_resource_link.rel_label,
|
|
533
|
+
)
|
|
187
534
|
|
|
188
|
-
rel_props_as_dict:
|
|
535
|
+
rel_props_as_dict: dict[str, PropertyRef] = _asdict_with_validate_relprops(
|
|
536
|
+
sub_resource_link,
|
|
537
|
+
)
|
|
189
538
|
|
|
190
539
|
attach_sub_resource_statement = sub_resource_attach_template.safe_substitute(
|
|
191
540
|
SubResourceLabel=sub_resource_link.target_node_label,
|
|
192
541
|
MatchClause=_build_match_clause(sub_resource_link.target_node_matcher),
|
|
193
542
|
RelMergeClause=rel_merge_clause,
|
|
543
|
+
module_name=_get_module_from_schema(sub_resource_link),
|
|
544
|
+
module_version=_get_cartography_version(),
|
|
194
545
|
SubResourceRelLabel=sub_resource_link.rel_label,
|
|
195
|
-
set_rel_properties_statement=_build_rel_properties_statement(
|
|
546
|
+
set_rel_properties_statement=_build_rel_properties_statement(
|
|
547
|
+
"r",
|
|
548
|
+
rel_props_as_dict,
|
|
549
|
+
),
|
|
196
550
|
)
|
|
197
551
|
return attach_sub_resource_statement
|
|
198
552
|
|
|
199
553
|
|
|
200
554
|
def _build_attach_additional_links_statement(
|
|
201
|
-
|
|
555
|
+
additional_relationships: OtherRelationships | None = None,
|
|
202
556
|
) -> str:
|
|
203
557
|
"""
|
|
204
558
|
Generates a Neo4j statement to attach one or more CartographyRelSchemas to node(s) previously mentioned in the
|
|
@@ -210,7 +564,7 @@ def _build_attach_additional_links_statement(
|
|
|
210
564
|
labels, attribute keys, and directionality. If additional_relationships is None, return an empty string.
|
|
211
565
|
"""
|
|
212
566
|
if not additional_relationships:
|
|
213
|
-
return
|
|
567
|
+
return ""
|
|
214
568
|
|
|
215
569
|
additional_links_template = Template(
|
|
216
570
|
"""
|
|
@@ -222,6 +576,8 @@ def _build_attach_additional_links_statement(
|
|
|
222
576
|
$RelMerge
|
|
223
577
|
ON CREATE SET $rel_var.firstseen = timestamp()
|
|
224
578
|
SET
|
|
579
|
+
$rel_var._module_name = "$module_name",
|
|
580
|
+
$rel_var._module_version = "$module_version",
|
|
225
581
|
$set_rel_properties_statement
|
|
226
582
|
""",
|
|
227
583
|
)
|
|
@@ -231,9 +587,13 @@ def _build_attach_additional_links_statement(
|
|
|
231
587
|
rel_var = f"r{num}"
|
|
232
588
|
|
|
233
589
|
if link.direction == LinkDirection.INWARD:
|
|
234
|
-
rel_merge_template = Template(
|
|
590
|
+
rel_merge_template = Template(
|
|
591
|
+
"""MERGE (i)<-[$rel_var:$AddlRelLabel]-($node_var)""",
|
|
592
|
+
)
|
|
235
593
|
else:
|
|
236
|
-
rel_merge_template = Template(
|
|
594
|
+
rel_merge_template = Template(
|
|
595
|
+
"""MERGE (i)-[$rel_var:$AddlRelLabel]->($node_var)""",
|
|
596
|
+
)
|
|
237
597
|
|
|
238
598
|
rel_merge = rel_merge_template.safe_substitute(
|
|
239
599
|
rel_var=rel_var,
|
|
@@ -245,20 +605,28 @@ def _build_attach_additional_links_statement(
|
|
|
245
605
|
|
|
246
606
|
additional_ref = additional_links_template.safe_substitute(
|
|
247
607
|
AddlLabel=link.target_node_label,
|
|
248
|
-
WhereClause=_build_where_clause_for_rel_match(
|
|
608
|
+
WhereClause=_build_where_clause_for_rel_match(
|
|
609
|
+
node_var,
|
|
610
|
+
link.target_node_matcher,
|
|
611
|
+
),
|
|
249
612
|
node_var=node_var,
|
|
250
613
|
rel_var=rel_var,
|
|
251
614
|
RelMerge=rel_merge,
|
|
252
|
-
|
|
615
|
+
module_name=_get_module_from_schema(link),
|
|
616
|
+
module_version=_get_cartography_version(),
|
|
617
|
+
set_rel_properties_statement=_build_rel_properties_statement(
|
|
618
|
+
rel_var,
|
|
619
|
+
rel_props_as_dict,
|
|
620
|
+
),
|
|
253
621
|
)
|
|
254
622
|
links.append(additional_ref)
|
|
255
623
|
|
|
256
|
-
return
|
|
624
|
+
return "UNION".join(links)
|
|
257
625
|
|
|
258
626
|
|
|
259
627
|
def _build_attach_relationships_statement(
|
|
260
|
-
|
|
261
|
-
|
|
628
|
+
sub_resource_relationship: CartographyRelSchema | None,
|
|
629
|
+
other_relationships: OtherRelationships | None,
|
|
262
630
|
) -> str:
|
|
263
631
|
"""
|
|
264
632
|
Use Neo4j subqueries to attach sub resource and/or other relationships.
|
|
@@ -271,14 +639,22 @@ def _build_attach_relationships_statement(
|
|
|
271
639
|
if not sub_resource_relationship and not other_relationships:
|
|
272
640
|
return ""
|
|
273
641
|
|
|
274
|
-
attach_sub_resource_statement = _build_attach_sub_resource_statement(
|
|
275
|
-
|
|
642
|
+
attach_sub_resource_statement = _build_attach_sub_resource_statement(
|
|
643
|
+
sub_resource_relationship,
|
|
644
|
+
)
|
|
645
|
+
attach_additional_links_statement = _build_attach_additional_links_statement(
|
|
646
|
+
other_relationships,
|
|
647
|
+
)
|
|
276
648
|
|
|
277
649
|
statements = []
|
|
278
|
-
statements +=
|
|
279
|
-
|
|
650
|
+
statements += (
|
|
651
|
+
[attach_sub_resource_statement] if attach_sub_resource_statement else []
|
|
652
|
+
)
|
|
653
|
+
statements += (
|
|
654
|
+
[attach_additional_links_statement] if attach_additional_links_statement else []
|
|
655
|
+
)
|
|
280
656
|
|
|
281
|
-
attach_relationships_statement =
|
|
657
|
+
attach_relationships_statement = "UNION".join(stmt for stmt in statements)
|
|
282
658
|
|
|
283
659
|
query_template = Template(
|
|
284
660
|
"""
|
|
@@ -288,12 +664,14 @@ def _build_attach_relationships_statement(
|
|
|
288
664
|
}
|
|
289
665
|
""",
|
|
290
666
|
)
|
|
291
|
-
return query_template.safe_substitute(
|
|
667
|
+
return query_template.safe_substitute(
|
|
668
|
+
attach_relationships_statement=attach_relationships_statement,
|
|
669
|
+
)
|
|
292
670
|
|
|
293
671
|
|
|
294
672
|
def rel_present_on_node_schema(
|
|
295
|
-
|
|
296
|
-
|
|
673
|
+
node_schema: CartographyNodeSchema,
|
|
674
|
+
rel_schema: CartographyRelSchema,
|
|
297
675
|
) -> bool:
|
|
298
676
|
"""
|
|
299
677
|
Answers the question: is the given rel_schema is present on the given node_schema?
|
|
@@ -305,9 +683,9 @@ def rel_present_on_node_schema(
|
|
|
305
683
|
|
|
306
684
|
|
|
307
685
|
def filter_selected_relationships(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
) ->
|
|
686
|
+
node_schema: CartographyNodeSchema,
|
|
687
|
+
selected_relationships: set[CartographyRelSchema],
|
|
688
|
+
) -> tuple[CartographyRelSchema | None, OtherRelationships | None]:
|
|
311
689
|
"""
|
|
312
690
|
Ensures that selected relationships specified to build_ingestion_query() are actually present on
|
|
313
691
|
node_schema.sub_resource_relationship and node_schema.other_relationships.
|
|
@@ -341,14 +719,16 @@ def filter_selected_relationships(
|
|
|
341
719
|
sub_resource_rel = None
|
|
342
720
|
|
|
343
721
|
# By this point, everything in selected_relationships is validated to be present in node_schema
|
|
344
|
-
filtered_other_rels = OtherRelationships(
|
|
722
|
+
filtered_other_rels = OtherRelationships(
|
|
723
|
+
[rel for rel in selected_relationships if rel != sub_resource_rel],
|
|
724
|
+
)
|
|
345
725
|
|
|
346
726
|
return sub_resource_rel, filtered_other_rels
|
|
347
727
|
|
|
348
728
|
|
|
349
729
|
def build_ingestion_query(
|
|
350
|
-
|
|
351
|
-
|
|
730
|
+
node_schema: CartographyNodeSchema,
|
|
731
|
+
selected_relationships: set[CartographyRelSchema] | None = None,
|
|
352
732
|
) -> str:
|
|
353
733
|
"""
|
|
354
734
|
Generates a Neo4j query from the given CartographyNodeSchema to ingest the specified nodes and relationships so that
|
|
@@ -375,59 +755,81 @@ def build_ingestion_query(
|
|
|
375
755
|
MERGE (i:$node_label{id: $dict_id_field})
|
|
376
756
|
ON CREATE SET i.firstseen = timestamp()
|
|
377
757
|
SET
|
|
758
|
+
i._module_name = "$module_name",
|
|
759
|
+
i._module_version = "$module_version",
|
|
378
760
|
$set_node_properties_statement
|
|
761
|
+
$set_ontology_node_properties_statement
|
|
379
762
|
$attach_relationships_statement
|
|
380
763
|
""",
|
|
381
764
|
)
|
|
382
765
|
|
|
383
766
|
node_props: CartographyNodeProperties = node_schema.properties
|
|
384
|
-
node_props_as_dict:
|
|
767
|
+
node_props_as_dict: dict[str, PropertyRef] = asdict(node_props)
|
|
385
768
|
|
|
386
769
|
# Handle selected relationships
|
|
387
|
-
sub_resource_rel:
|
|
388
|
-
|
|
770
|
+
sub_resource_rel: CartographyRelSchema | None = (
|
|
771
|
+
node_schema.sub_resource_relationship
|
|
772
|
+
)
|
|
773
|
+
other_rels: OtherRelationships | None = node_schema.other_relationships
|
|
389
774
|
if selected_relationships or selected_relationships == set():
|
|
390
|
-
sub_resource_rel, other_rels = filter_selected_relationships(
|
|
775
|
+
sub_resource_rel, other_rels = filter_selected_relationships(
|
|
776
|
+
node_schema,
|
|
777
|
+
selected_relationships,
|
|
778
|
+
)
|
|
391
779
|
|
|
392
780
|
ingest_query = query_template.safe_substitute(
|
|
393
781
|
node_label=node_schema.label,
|
|
394
782
|
dict_id_field=node_props.id,
|
|
783
|
+
module_name=_get_module_from_schema(node_schema),
|
|
784
|
+
module_version=_get_cartography_version(),
|
|
395
785
|
set_node_properties_statement=_build_node_properties_statement(
|
|
396
786
|
node_props_as_dict,
|
|
397
787
|
node_schema.extra_node_labels,
|
|
398
788
|
),
|
|
399
|
-
|
|
789
|
+
set_ontology_node_properties_statement=_build_ontology_node_properties_statement(
|
|
790
|
+
node_schema,
|
|
791
|
+
node_props_as_dict,
|
|
792
|
+
),
|
|
793
|
+
attach_relationships_statement=_build_attach_relationships_statement(
|
|
794
|
+
sub_resource_rel,
|
|
795
|
+
other_rels,
|
|
796
|
+
),
|
|
400
797
|
)
|
|
401
798
|
return ingest_query
|
|
402
799
|
|
|
403
800
|
|
|
404
|
-
def build_create_index_queries(node_schema: CartographyNodeSchema) ->
|
|
801
|
+
def build_create_index_queries(node_schema: CartographyNodeSchema) -> list[str]:
|
|
405
802
|
"""
|
|
406
803
|
Generate queries to create indexes for the given CartographyNodeSchema and all node types attached to it via its
|
|
407
804
|
relationships.
|
|
408
805
|
:param node_schema: The Cartography node_schema object
|
|
409
806
|
:return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
|
|
410
807
|
"""
|
|
411
|
-
index_template = Template(
|
|
808
|
+
index_template = Template(
|
|
809
|
+
"CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute);",
|
|
810
|
+
)
|
|
412
811
|
|
|
413
812
|
# First ensure an index exists for the node_schema and all extra labels on the `id` and `lastupdated` fields
|
|
414
813
|
result = [
|
|
415
814
|
index_template.safe_substitute(
|
|
416
815
|
TargetNodeLabel=node_schema.label,
|
|
417
|
-
TargetAttribute=
|
|
816
|
+
TargetAttribute="id",
|
|
418
817
|
),
|
|
419
818
|
index_template.safe_substitute(
|
|
420
819
|
TargetNodeLabel=node_schema.label,
|
|
421
|
-
TargetAttribute=
|
|
820
|
+
TargetAttribute="lastupdated",
|
|
422
821
|
),
|
|
423
822
|
]
|
|
424
823
|
if node_schema.extra_node_labels:
|
|
425
|
-
result.extend(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
824
|
+
result.extend(
|
|
825
|
+
[
|
|
826
|
+
index_template.safe_substitute(
|
|
827
|
+
TargetNodeLabel=label,
|
|
828
|
+
TargetAttribute="id", # Precondition: 'id' is defined on all cartography node_schema objects.
|
|
829
|
+
)
|
|
830
|
+
for label in node_schema.extra_node_labels.labels
|
|
831
|
+
],
|
|
832
|
+
)
|
|
431
833
|
|
|
432
834
|
# Next, for all relationships possible out of this node, ensure that indexes exist for all target nodes' properties
|
|
433
835
|
# as specified in their TargetNodeMatchers.
|
|
@@ -439,15 +841,211 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
|
|
|
439
841
|
for rs in rel_schemas:
|
|
440
842
|
for target_key in asdict(rs.target_node_matcher).keys():
|
|
441
843
|
result.append(
|
|
442
|
-
index_template.safe_substitute(
|
|
844
|
+
index_template.safe_substitute(
|
|
845
|
+
TargetNodeLabel=rs.target_node_label,
|
|
846
|
+
TargetAttribute=target_key,
|
|
847
|
+
),
|
|
443
848
|
)
|
|
444
849
|
|
|
445
850
|
# Now, include extra indexes defined by the module author on the node schema's property refs.
|
|
446
|
-
node_props_as_dict:
|
|
447
|
-
result.extend(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
851
|
+
node_props_as_dict: dict[str, PropertyRef] = asdict(node_schema.properties)
|
|
852
|
+
result.extend(
|
|
853
|
+
[
|
|
854
|
+
index_template.safe_substitute(
|
|
855
|
+
TargetNodeLabel=node_schema.label,
|
|
856
|
+
TargetAttribute=prop_name,
|
|
857
|
+
)
|
|
858
|
+
for prop_name, prop_ref in node_props_as_dict.items()
|
|
859
|
+
if prop_ref.extra_index
|
|
860
|
+
],
|
|
861
|
+
)
|
|
453
862
|
return result
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def build_create_index_queries_for_matchlink(
|
|
866
|
+
rel_schema: CartographyRelSchema,
|
|
867
|
+
) -> list[str]:
|
|
868
|
+
"""
|
|
869
|
+
Generate queries to create indexes for the given CartographyRelSchema and all node types attached to it via its
|
|
870
|
+
relationships.
|
|
871
|
+
:param rel_schema: The CartographyRelSchema object
|
|
872
|
+
:return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
|
|
873
|
+
"""
|
|
874
|
+
if not rel_schema.source_node_matcher:
|
|
875
|
+
logger.warning(
|
|
876
|
+
f"No source node matcher found for {rel_schema.rel_label}; returning empty list."
|
|
877
|
+
"Please note that build_create_index_queries_for_matchlink() is only used for load_matchlinks() where we match on "
|
|
878
|
+
"and connect existing nodes in the graph."
|
|
879
|
+
)
|
|
880
|
+
return []
|
|
881
|
+
|
|
882
|
+
index_template = Template(
|
|
883
|
+
"CREATE INDEX IF NOT EXISTS FOR (n:$NodeLabel) ON (n.$NodeAttribute);",
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
result = []
|
|
887
|
+
for source_key in asdict(rel_schema.source_node_matcher).keys():
|
|
888
|
+
result.append(
|
|
889
|
+
index_template.safe_substitute(
|
|
890
|
+
NodeLabel=rel_schema.source_node_label,
|
|
891
|
+
NodeAttribute=source_key,
|
|
892
|
+
),
|
|
893
|
+
)
|
|
894
|
+
for target_key in asdict(rel_schema.target_node_matcher).keys():
|
|
895
|
+
result.append(
|
|
896
|
+
index_template.safe_substitute(
|
|
897
|
+
NodeLabel=rel_schema.target_node_label,
|
|
898
|
+
NodeAttribute=target_key,
|
|
899
|
+
),
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
# Create a composite index for the relationship between the source and target nodes.
|
|
903
|
+
# https://neo4j.com/docs/cypher-manual/4.3/indexes-for-search-performance/#administration-indexes-create-a-composite-index-for-relationships
|
|
904
|
+
rel_index_template = Template(
|
|
905
|
+
"CREATE INDEX IF NOT EXISTS FOR ()$rel_direction[r:$RelLabel]$rel_direction_end() "
|
|
906
|
+
"ON (r.lastupdated, r._sub_resource_label, r._sub_resource_id);",
|
|
907
|
+
)
|
|
908
|
+
if rel_schema.direction == LinkDirection.INWARD:
|
|
909
|
+
result.append(
|
|
910
|
+
rel_index_template.safe_substitute(
|
|
911
|
+
RelLabel=rel_schema.rel_label,
|
|
912
|
+
rel_direction="<-",
|
|
913
|
+
rel_direction_end="-",
|
|
914
|
+
)
|
|
915
|
+
)
|
|
916
|
+
else:
|
|
917
|
+
result.append(
|
|
918
|
+
rel_index_template.safe_substitute(
|
|
919
|
+
RelLabel=rel_schema.rel_label,
|
|
920
|
+
rel_direction="-",
|
|
921
|
+
rel_direction_end="->",
|
|
922
|
+
)
|
|
923
|
+
)
|
|
924
|
+
return result
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def build_matchlink_query(rel_schema: CartographyRelSchema) -> str:
|
|
928
|
+
"""
|
|
929
|
+
Generate a Neo4j query to link two existing nodes when given a CartographyRelSchema object.
|
|
930
|
+
This is only used for load_matchlinks().
|
|
931
|
+
:param rel_schema: The CartographyRelSchema object to generate a query. This CartographyRelSchema object
|
|
932
|
+
- Must have a source_node_matcher and source_node_label defined
|
|
933
|
+
- Must have a CartographyRelProperties object where _sub_resource_label and _sub_resource_id are defined
|
|
934
|
+
:return: A Neo4j query that can be used to link two existing nodes.
|
|
935
|
+
"""
|
|
936
|
+
if not rel_schema.source_node_matcher or not rel_schema.source_node_label:
|
|
937
|
+
raise ValueError(
|
|
938
|
+
f"No source node matcher or source node label found for {rel_schema.rel_label}. "
|
|
939
|
+
"MatchLink relationships require a source_node_matcher and source_node_label to be defined."
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
rel_props_as_dict = _asdict_with_validate_relprops(rel_schema)
|
|
943
|
+
|
|
944
|
+
# These are needed for the cleanup query
|
|
945
|
+
if "_sub_resource_label" not in rel_props_as_dict:
|
|
946
|
+
raise ValueError(
|
|
947
|
+
f"Expected _sub_resource_label to be defined on {rel_schema.properties.__class__.__name__}"
|
|
948
|
+
"Please include `_sub_resource_label: PropertyRef = PropertyRef('_sub_resource_label', set_in_kwargs=True)`"
|
|
949
|
+
)
|
|
950
|
+
if "_sub_resource_id" not in rel_props_as_dict:
|
|
951
|
+
raise ValueError(
|
|
952
|
+
f"Expected _sub_resource_id to be defined on {rel_schema.properties.__class__.__name__}"
|
|
953
|
+
"Please include `_sub_resource_id: PropertyRef = PropertyRef('_sub_resource_id', set_in_kwargs=True)`"
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
matchlink_query_template = Template(
|
|
957
|
+
"""
|
|
958
|
+
UNWIND $DictList as item
|
|
959
|
+
$source_match
|
|
960
|
+
$target_match
|
|
961
|
+
MERGE $rel
|
|
962
|
+
ON CREATE SET r.firstseen = timestamp()
|
|
963
|
+
SET
|
|
964
|
+
r._module_name = "$module_name",
|
|
965
|
+
r._module_version = "$module_version",
|
|
966
|
+
$set_rel_properties_statement;
|
|
967
|
+
"""
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
source_match = Template(
|
|
971
|
+
"MATCH (from:$source_node_label{$match_clause})"
|
|
972
|
+
).safe_substitute(
|
|
973
|
+
source_node_label=rel_schema.source_node_label,
|
|
974
|
+
match_clause=_build_match_clause(rel_schema.source_node_matcher),
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
target_match = Template(
|
|
978
|
+
"MATCH (to:$target_node_label{$match_clause})"
|
|
979
|
+
).safe_substitute(
|
|
980
|
+
target_node_label=rel_schema.target_node_label,
|
|
981
|
+
match_clause=_build_match_clause(rel_schema.target_node_matcher),
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
if rel_schema.direction == LinkDirection.INWARD:
|
|
985
|
+
rel = f"(from)<-[r:{rel_schema.rel_label}]-(to)"
|
|
986
|
+
else:
|
|
987
|
+
rel = f"(from)-[r:{rel_schema.rel_label}]->(to)"
|
|
988
|
+
|
|
989
|
+
return matchlink_query_template.safe_substitute(
|
|
990
|
+
source_match=source_match,
|
|
991
|
+
target_match=target_match,
|
|
992
|
+
rel=rel,
|
|
993
|
+
module_name=_get_module_from_schema(rel_schema),
|
|
994
|
+
module_version=_get_cartography_version(),
|
|
995
|
+
set_rel_properties_statement=_build_rel_properties_statement(
|
|
996
|
+
"r",
|
|
997
|
+
rel_props_as_dict,
|
|
998
|
+
),
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def _get_cartography_version() -> str:
|
|
1003
|
+
"""
|
|
1004
|
+
Get the current version of the cartography package.
|
|
1005
|
+
|
|
1006
|
+
This function attempts to retrieve the version of the installed cartography package
|
|
1007
|
+
using importlib.metadata. If the package is not found (typically in development
|
|
1008
|
+
or testing environments), it returns 'dev' as a fallback.
|
|
1009
|
+
|
|
1010
|
+
Returns:
|
|
1011
|
+
The version string of the cartography package, or 'dev' if not found
|
|
1012
|
+
"""
|
|
1013
|
+
try:
|
|
1014
|
+
return version("cartography")
|
|
1015
|
+
except PackageNotFoundError:
|
|
1016
|
+
# This can occured if the cartography package is not installed in the environment, typically in development or testing environments.
|
|
1017
|
+
logger.warning("cartography package not found. Returning 'dev' version.")
|
|
1018
|
+
# Fallback to reading the VERSION file if the package is not found
|
|
1019
|
+
return "dev"
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
def _get_module_from_schema(
|
|
1023
|
+
schema, #: "CartographyNodeSchema" | "CartographyRelSchema",
|
|
1024
|
+
) -> str:
|
|
1025
|
+
"""
|
|
1026
|
+
Extract the module name from a Cartography schema object.
|
|
1027
|
+
|
|
1028
|
+
This function extracts and formats the module name from a CartographyNodeSchema
|
|
1029
|
+
or CartographyRelSchema object. It expects schemas to be part of the official
|
|
1030
|
+
cartography.models package hierarchy and returns a formatted string indicating
|
|
1031
|
+
the specific cartography module.
|
|
1032
|
+
|
|
1033
|
+
Args:
|
|
1034
|
+
schema: A CartographyNodeSchema or CartographyRelSchema object
|
|
1035
|
+
|
|
1036
|
+
Returns:
|
|
1037
|
+
A formatted module name string in the format 'cartography:<module_name>'
|
|
1038
|
+
or 'unknown:<full_module_path>' if the schema is not from cartography.models
|
|
1039
|
+
"""
|
|
1040
|
+
# If the entity schema does not belong to the cartography.models package,
|
|
1041
|
+
# we log a warning and return the full module path.
|
|
1042
|
+
if not schema.__module__.startswith("cartography.models."):
|
|
1043
|
+
logger.warning(
|
|
1044
|
+
"The schema %s does not start with 'cartography.models.'. "
|
|
1045
|
+
"This may indicate that the schema is not part of the official cartography models.",
|
|
1046
|
+
schema.__module__,
|
|
1047
|
+
)
|
|
1048
|
+
return f"unknown:{schema.__module__}"
|
|
1049
|
+
# Otherwise, we return the module path as a string.
|
|
1050
|
+
parts = schema.__module__.split(".")
|
|
1051
|
+
return f"cartography:{parts[2]}"
|