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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for Spacelift GraphQL API interactions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# Timeout for API calls: (connection timeout, read timeout) in seconds
|
|
13
|
+
_TIMEOUT = (60, 60)
|
|
14
|
+
|
|
15
|
+
# GraphQL mutation to exchange API key ID and secret for a JWT token
|
|
16
|
+
_TOKEN_EXCHANGE_MUTATION = """
|
|
17
|
+
mutation GetSpaceliftToken($id: ID!, $secret: String!) {
|
|
18
|
+
apiKeyUser(id: $id, secret: $secret) {
|
|
19
|
+
jwt
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def call_spacelift_api(
|
|
26
|
+
session: requests.Session,
|
|
27
|
+
api_endpoint: str,
|
|
28
|
+
query: str,
|
|
29
|
+
variables: dict[str, Any] | None = None,
|
|
30
|
+
) -> dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Make a GraphQL query to the Spacelift API.
|
|
33
|
+
"""
|
|
34
|
+
logger.debug(f"Making GraphQL request to {api_endpoint}")
|
|
35
|
+
|
|
36
|
+
# Prepare the GraphQL request payload
|
|
37
|
+
payload: dict[str, Any] = {"query": query}
|
|
38
|
+
if variables:
|
|
39
|
+
payload["variables"] = variables
|
|
40
|
+
|
|
41
|
+
# Make the POST request to the GraphQL endpoint
|
|
42
|
+
response = session.post(
|
|
43
|
+
api_endpoint,
|
|
44
|
+
json=payload,
|
|
45
|
+
timeout=_TIMEOUT,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Raise an exception for HTTP errors (4xx, 5xx)
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
|
|
51
|
+
# Parse the JSON response
|
|
52
|
+
result = response.json()
|
|
53
|
+
|
|
54
|
+
# Check for GraphQL errors in the response
|
|
55
|
+
if "errors" in result:
|
|
56
|
+
error_messages = [
|
|
57
|
+
error.get("message", "Unknown error") for error in result["errors"]
|
|
58
|
+
]
|
|
59
|
+
error_string = "; ".join(error_messages)
|
|
60
|
+
raise ValueError(f"GraphQL query failed: {error_string}")
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_spacelift_token(api_endpoint: str, key_id: str, key_secret: str) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Exchange Spacelift API key ID and secret for a JWT bearer token.
|
|
68
|
+
|
|
69
|
+
This uses the Spacelift GraphQL API's apiKeyUser mutation to obtain a JWT token
|
|
70
|
+
that can be used for authenticated requests. This is the recommended authentication
|
|
71
|
+
method as it avoids storing long-lived tokens.
|
|
72
|
+
|
|
73
|
+
:param api_endpoint: Spacelift GraphQL API endpoint (e.g., https://your-account.app.spacelift.io/graphql)
|
|
74
|
+
:param key_id: Spacelift API key ID (26-character ULID)
|
|
75
|
+
:param key_secret: Spacelift API key secret
|
|
76
|
+
:return: JWT bearer token string
|
|
77
|
+
:raises: requests.exceptions.RequestException if the API request fails
|
|
78
|
+
:raises: ValueError if the response doesn't contain a valid token
|
|
79
|
+
"""
|
|
80
|
+
logger.info("Exchanging Spacelift API key for JWT token")
|
|
81
|
+
|
|
82
|
+
payload = {
|
|
83
|
+
"query": _TOKEN_EXCHANGE_MUTATION,
|
|
84
|
+
"variables": {
|
|
85
|
+
"id": key_id,
|
|
86
|
+
"secret": key_secret,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
response = requests.post(
|
|
92
|
+
api_endpoint,
|
|
93
|
+
json=payload,
|
|
94
|
+
headers={"Content-Type": "application/json"},
|
|
95
|
+
timeout=_TIMEOUT,
|
|
96
|
+
)
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
|
|
99
|
+
data: dict[str, Any] = response.json()
|
|
100
|
+
|
|
101
|
+
# Check for GraphQL errors
|
|
102
|
+
if "errors" in data:
|
|
103
|
+
error_messages = [
|
|
104
|
+
error.get("message", "Unknown error") for error in data["errors"]
|
|
105
|
+
]
|
|
106
|
+
error_string = "; ".join(error_messages)
|
|
107
|
+
raise ValueError(f"Token exchange failed: {error_string}")
|
|
108
|
+
|
|
109
|
+
# Extract JWT token from response
|
|
110
|
+
jwt = data.get("data", {}).get("apiKeyUser", {}).get("jwt")
|
|
111
|
+
if not jwt:
|
|
112
|
+
raise ValueError("Token exchange response did not contain a JWT token")
|
|
113
|
+
|
|
114
|
+
logger.info("Successfully obtained JWT token from Spacelift API")
|
|
115
|
+
return jwt
|
|
116
|
+
|
|
117
|
+
except requests.exceptions.RequestException as e:
|
|
118
|
+
logger.error(f"Failed to exchange Spacelift API key for token: {e}")
|
|
119
|
+
raise
|
|
120
|
+
except (KeyError, ValueError) as e:
|
|
121
|
+
logger.error(f"Invalid response from Spacelift token exchange: {e}")
|
|
122
|
+
raise
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import neo4j
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from cartography.client.core.tx import load
|
|
8
|
+
from cartography.graph.job import GraphJob
|
|
9
|
+
from cartography.intel.spacelift.util import call_spacelift_api
|
|
10
|
+
from cartography.models.spacelift.workerpool import SpaceliftWorkerPoolSchema
|
|
11
|
+
from cartography.util import timeit
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# GraphQL query to fetch all worker pools (direct array, no pagination)
|
|
16
|
+
GET_WORKER_POOLS_QUERY = """
|
|
17
|
+
query {
|
|
18
|
+
workerPools {
|
|
19
|
+
id
|
|
20
|
+
name
|
|
21
|
+
description
|
|
22
|
+
space
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@timeit
|
|
29
|
+
def get_worker_pools(
|
|
30
|
+
session: requests.Session, api_endpoint: str
|
|
31
|
+
) -> list[dict[str, Any]]:
|
|
32
|
+
|
|
33
|
+
logger.info("Fetching Spacelift worker pools")
|
|
34
|
+
|
|
35
|
+
response = call_spacelift_api(session, api_endpoint, GET_WORKER_POOLS_QUERY)
|
|
36
|
+
worker_pools_data = response.get("data", {}).get("workerPools", [])
|
|
37
|
+
|
|
38
|
+
logger.info(f"Retrieved {len(worker_pools_data)} Spacelift worker pools")
|
|
39
|
+
return worker_pools_data
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def transform_worker_pools(
|
|
43
|
+
worker_pools_data: list[dict[str, Any]], account_id: str
|
|
44
|
+
) -> list[dict[str, Any]]:
|
|
45
|
+
result: list[dict[str, Any]] = []
|
|
46
|
+
|
|
47
|
+
for pool in worker_pools_data:
|
|
48
|
+
transformed_pool = {
|
|
49
|
+
"id": pool["id"],
|
|
50
|
+
"name": pool.get("name"),
|
|
51
|
+
"description": pool.get("description"),
|
|
52
|
+
"pool_type": None, # WorkerPool type doesn't have a 'type' field in the API
|
|
53
|
+
"space_id": pool.get("space"),
|
|
54
|
+
"spacelift_account_id": account_id,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
result.append(transformed_pool)
|
|
58
|
+
|
|
59
|
+
logger.info(f"Transformed {len(result)} worker pools")
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def load_worker_pools(
|
|
64
|
+
neo4j_session: neo4j.Session,
|
|
65
|
+
worker_pools_data: list[dict[str, Any]],
|
|
66
|
+
update_tag: int,
|
|
67
|
+
account_id: str,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Load Spacelift worker pools data into Neo4j using the data model.
|
|
71
|
+
"""
|
|
72
|
+
load(
|
|
73
|
+
neo4j_session,
|
|
74
|
+
SpaceliftWorkerPoolSchema(),
|
|
75
|
+
worker_pools_data,
|
|
76
|
+
lastupdated=update_tag,
|
|
77
|
+
spacelift_account_id=account_id,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
logger.info(f"Loaded {len(worker_pools_data)} Spacelift worker pools")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@timeit
|
|
84
|
+
def cleanup_worker_pools(
|
|
85
|
+
neo4j_session: neo4j.Session,
|
|
86
|
+
common_job_parameters: dict[str, Any],
|
|
87
|
+
) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Remove stale Spacelift worker pool data from Neo4j.
|
|
90
|
+
"""
|
|
91
|
+
logger.debug("Running SpaceliftWorkerPool cleanup job")
|
|
92
|
+
GraphJob.from_node_schema(SpaceliftWorkerPoolSchema(), common_job_parameters).run(
|
|
93
|
+
neo4j_session
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@timeit
|
|
98
|
+
def sync_worker_pools(
|
|
99
|
+
neo4j_session: neo4j.Session,
|
|
100
|
+
spacelift_session: requests.Session,
|
|
101
|
+
api_endpoint: str,
|
|
102
|
+
account_id: str,
|
|
103
|
+
common_job_parameters: dict[str, Any],
|
|
104
|
+
) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Sync Spacelift worker pools data to Neo4j.
|
|
107
|
+
|
|
108
|
+
:param neo4j_session: Neo4j session
|
|
109
|
+
:param spacelift_session: Authenticated requests session for Spacelift API
|
|
110
|
+
:param api_endpoint: Spacelift GraphQL API endpoint
|
|
111
|
+
:param account_id: The Spacelift account ID
|
|
112
|
+
:param common_job_parameters: Common job parameters containing UPDATE_TAG
|
|
113
|
+
"""
|
|
114
|
+
# 1. GET - Fetch data from Spacelift API
|
|
115
|
+
worker_pools_raw_data = get_worker_pools(spacelift_session, api_endpoint)
|
|
116
|
+
|
|
117
|
+
# 2. TRANSFORM - Shape data for ingestion
|
|
118
|
+
transformed_worker_pools = transform_worker_pools(worker_pools_raw_data, account_id)
|
|
119
|
+
|
|
120
|
+
# 3. LOAD - Ingest to Neo4j using data model
|
|
121
|
+
load_worker_pools(
|
|
122
|
+
neo4j_session,
|
|
123
|
+
transformed_worker_pools,
|
|
124
|
+
common_job_parameters["UPDATE_TAG"],
|
|
125
|
+
account_id,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# 4. CLEANUP - Remove stale data
|
|
129
|
+
cleanup_worker_pools(neo4j_session, common_job_parameters)
|
|
130
|
+
|
|
131
|
+
logger.info(f"Synced {len(transformed_worker_pools)} Spacelift worker pools")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import neo4j
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from cartography.client.core.tx import load
|
|
8
|
+
from cartography.graph.job import GraphJob
|
|
9
|
+
from cartography.intel.spacelift.util import call_spacelift_api
|
|
10
|
+
from cartography.models.spacelift.worker import SpaceliftWorkerSchema
|
|
11
|
+
from cartography.util import timeit
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# GraphQL query to fetch workers nested under workerPools
|
|
16
|
+
# Note: Workers don't have a top-level query, they're nested under workerPools
|
|
17
|
+
# Note: Worker type doesn't have a 'name' field, only 'id' and 'status'
|
|
18
|
+
GET_WORKERS_QUERY = """
|
|
19
|
+
query {
|
|
20
|
+
workerPools {
|
|
21
|
+
id
|
|
22
|
+
workers {
|
|
23
|
+
id
|
|
24
|
+
status
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@timeit
|
|
32
|
+
def get_workers(session: requests.Session, api_endpoint: str) -> list[dict[str, Any]]:
|
|
33
|
+
"""
|
|
34
|
+
Fetch all Spacelift workers from the API.
|
|
35
|
+
Workers are nested under workerPools, so we query workerPools and flatten the workers.
|
|
36
|
+
"""
|
|
37
|
+
logger.info("Fetching Spacelift workers")
|
|
38
|
+
|
|
39
|
+
response = call_spacelift_api(session, api_endpoint, GET_WORKERS_QUERY)
|
|
40
|
+
worker_pools = response.get("data", {}).get("workerPools", [])
|
|
41
|
+
|
|
42
|
+
# Flatten workers from all pools and add the pool ID to each worker
|
|
43
|
+
all_workers = []
|
|
44
|
+
for pool in worker_pools:
|
|
45
|
+
pool_id = pool["id"]
|
|
46
|
+
for worker in pool.get("workers", []):
|
|
47
|
+
# Add the pool ID to each worker
|
|
48
|
+
worker["workerPool"] = pool_id
|
|
49
|
+
all_workers.append(worker)
|
|
50
|
+
|
|
51
|
+
logger.info(
|
|
52
|
+
f"Retrieved {len(all_workers)} Spacelift workers from {len(worker_pools)} worker pools"
|
|
53
|
+
)
|
|
54
|
+
return all_workers
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def transform_workers(
|
|
58
|
+
workers_data: list[dict[str, Any]], account_id: str
|
|
59
|
+
) -> list[dict[str, Any]]:
|
|
60
|
+
result: list[dict[str, Any]] = []
|
|
61
|
+
|
|
62
|
+
for worker in workers_data:
|
|
63
|
+
worker_id = worker["id"]
|
|
64
|
+
|
|
65
|
+
transformed_worker = {
|
|
66
|
+
"id": worker_id,
|
|
67
|
+
"name": worker_id, # Use ID as name since Worker type doesn't have a name field
|
|
68
|
+
"status": worker.get("status"),
|
|
69
|
+
"worker_pool_id": worker.get("workerPool"),
|
|
70
|
+
"spacelift_account_id": account_id,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
result.append(transformed_worker)
|
|
74
|
+
|
|
75
|
+
logger.info(f"Transformed {len(result)} workers")
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_workers(
|
|
80
|
+
neo4j_session: neo4j.Session,
|
|
81
|
+
workers_data: list[dict[str, Any]],
|
|
82
|
+
update_tag: int,
|
|
83
|
+
account_id: str,
|
|
84
|
+
) -> None:
|
|
85
|
+
|
|
86
|
+
load(
|
|
87
|
+
neo4j_session,
|
|
88
|
+
SpaceliftWorkerSchema(),
|
|
89
|
+
workers_data,
|
|
90
|
+
lastupdated=update_tag,
|
|
91
|
+
spacelift_account_id=account_id,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
logger.info(f"Loaded {len(workers_data)} Spacelift workers")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@timeit
|
|
98
|
+
def cleanup_workers(
|
|
99
|
+
neo4j_session: neo4j.Session,
|
|
100
|
+
common_job_parameters: dict[str, Any],
|
|
101
|
+
) -> None:
|
|
102
|
+
|
|
103
|
+
logger.debug("Running SpaceliftWorker cleanup job")
|
|
104
|
+
GraphJob.from_node_schema(SpaceliftWorkerSchema(), common_job_parameters).run(
|
|
105
|
+
neo4j_session
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@timeit
|
|
110
|
+
def sync_workers(
|
|
111
|
+
neo4j_session: neo4j.Session,
|
|
112
|
+
spacelift_session: requests.Session,
|
|
113
|
+
api_endpoint: str,
|
|
114
|
+
account_id: str,
|
|
115
|
+
common_job_parameters: dict[str, Any],
|
|
116
|
+
) -> None:
|
|
117
|
+
|
|
118
|
+
workers_raw_data = get_workers(spacelift_session, api_endpoint)
|
|
119
|
+
transformed_workers = transform_workers(workers_raw_data, account_id)
|
|
120
|
+
load_workers(
|
|
121
|
+
neo4j_session,
|
|
122
|
+
transformed_workers,
|
|
123
|
+
common_job_parameters["UPDATE_TAG"],
|
|
124
|
+
account_id,
|
|
125
|
+
)
|
|
126
|
+
cleanup_workers(neo4j_session, common_job_parameters)
|
|
127
|
+
|
|
128
|
+
logger.info(f"Synced {len(transformed_workers)} Spacelift workers")
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
from neo4j import Session
|
|
7
|
+
|
|
8
|
+
from cartography.client.aws import list_accounts
|
|
9
|
+
from cartography.client.aws.ecr import get_ecr_images
|
|
10
|
+
from cartography.config import Config
|
|
11
|
+
from cartography.intel.trivy.scanner import cleanup
|
|
12
|
+
from cartography.intel.trivy.scanner import get_json_files_in_dir
|
|
13
|
+
from cartography.intel.trivy.scanner import get_json_files_in_s3
|
|
14
|
+
from cartography.intel.trivy.scanner import sync_single_image
|
|
15
|
+
from cartography.stats import get_stats_client
|
|
16
|
+
from cartography.util import timeit
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
stat_handler = get_stats_client("trivy.scanner")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_scan_targets_and_aliases(
|
|
23
|
+
neo4j_session: Session,
|
|
24
|
+
account_ids: list[str] | None = None,
|
|
25
|
+
) -> tuple[set[str], dict[str, str]]:
|
|
26
|
+
"""
|
|
27
|
+
Return tag URIs and a mapping of digest-qualified URIs to tag URIs.
|
|
28
|
+
"""
|
|
29
|
+
if not account_ids:
|
|
30
|
+
aws_accounts = list_accounts(neo4j_session)
|
|
31
|
+
else:
|
|
32
|
+
aws_accounts = account_ids
|
|
33
|
+
|
|
34
|
+
image_uris: set[str] = set()
|
|
35
|
+
digest_aliases: dict[str, str] = {}
|
|
36
|
+
|
|
37
|
+
for account_id in aws_accounts:
|
|
38
|
+
for _, _, image_uri, _, digest in get_ecr_images(neo4j_session, account_id):
|
|
39
|
+
if not image_uri:
|
|
40
|
+
continue
|
|
41
|
+
image_uris.add(image_uri)
|
|
42
|
+
if digest:
|
|
43
|
+
# repo URI is everything before the trailing ":" (if present)
|
|
44
|
+
repo_uri = image_uri.rsplit(":", 1)[0]
|
|
45
|
+
digest_uri = f"{repo_uri}@{digest}"
|
|
46
|
+
digest_aliases[digest_uri] = image_uri
|
|
47
|
+
|
|
48
|
+
return image_uris, digest_aliases
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@timeit
|
|
52
|
+
def get_scan_targets(
|
|
53
|
+
neo4j_session: Session,
|
|
54
|
+
account_ids: list[str] | None = None,
|
|
55
|
+
) -> set[str]:
|
|
56
|
+
"""
|
|
57
|
+
Return list of ECR images from all accounts in the graph.
|
|
58
|
+
"""
|
|
59
|
+
image_uris, _ = _get_scan_targets_and_aliases(neo4j_session, account_ids)
|
|
60
|
+
return image_uris
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _prepare_trivy_data(
|
|
64
|
+
trivy_data: dict[str, Any],
|
|
65
|
+
image_uris: set[str],
|
|
66
|
+
digest_aliases: dict[str, str],
|
|
67
|
+
source: str,
|
|
68
|
+
) -> tuple[dict[str, Any], str] | None:
|
|
69
|
+
"""
|
|
70
|
+
Determine the tag URI that corresponds to this Trivy payload.
|
|
71
|
+
|
|
72
|
+
Returns (trivy_data, display_uri) if the payload can be linked to an image present
|
|
73
|
+
in the graph; otherwise returns None so the caller can skip ingestion.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
artifact_name = (trivy_data.get("ArtifactName") or "").strip()
|
|
77
|
+
metadata = trivy_data.get("Metadata") or {}
|
|
78
|
+
candidates: list[str] = []
|
|
79
|
+
|
|
80
|
+
if artifact_name:
|
|
81
|
+
candidates.append(artifact_name)
|
|
82
|
+
|
|
83
|
+
repo_tags = metadata.get("RepoTags", [])
|
|
84
|
+
repo_digests = metadata.get("RepoDigests", [])
|
|
85
|
+
stripped_tags_digests = [item.strip() for item in repo_tags + repo_digests]
|
|
86
|
+
candidates.extend(stripped_tags_digests)
|
|
87
|
+
|
|
88
|
+
display_uri: str | None = None
|
|
89
|
+
|
|
90
|
+
for candidate in candidates:
|
|
91
|
+
if not candidate:
|
|
92
|
+
continue
|
|
93
|
+
if candidate in image_uris:
|
|
94
|
+
display_uri = candidate
|
|
95
|
+
break
|
|
96
|
+
alias = digest_aliases.get(candidate)
|
|
97
|
+
if alias:
|
|
98
|
+
display_uri = alias
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
if not display_uri:
|
|
102
|
+
logger.debug(
|
|
103
|
+
"Skipping Trivy results for %s because no matching image URI was found in the graph",
|
|
104
|
+
source,
|
|
105
|
+
)
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
return trivy_data, display_uri
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@timeit
|
|
112
|
+
def sync_trivy_aws_ecr_from_s3(
|
|
113
|
+
neo4j_session: Session,
|
|
114
|
+
trivy_s3_bucket: str,
|
|
115
|
+
trivy_s3_prefix: str,
|
|
116
|
+
update_tag: int,
|
|
117
|
+
common_job_parameters: dict[str, Any],
|
|
118
|
+
boto3_session: boto3.Session,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Sync Trivy scan results from S3 for AWS ECR images.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
neo4j_session: Neo4j session for database operations
|
|
125
|
+
trivy_s3_bucket: S3 bucket containing scan results
|
|
126
|
+
trivy_s3_prefix: S3 prefix path containing scan results
|
|
127
|
+
update_tag: Update tag for tracking
|
|
128
|
+
common_job_parameters: Common job parameters for cleanup
|
|
129
|
+
boto3_session: boto3 session for S3 operations
|
|
130
|
+
"""
|
|
131
|
+
logger.info(
|
|
132
|
+
f"Using Trivy scan results from s3://{trivy_s3_bucket}/{trivy_s3_prefix}"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
image_uris, digest_aliases = _get_scan_targets_and_aliases(neo4j_session)
|
|
136
|
+
json_files: set[str] = get_json_files_in_s3(
|
|
137
|
+
trivy_s3_bucket, trivy_s3_prefix, boto3_session
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if len(json_files) == 0:
|
|
141
|
+
logger.error(
|
|
142
|
+
f"Trivy sync was configured, but there are no ECR images with S3 json scan results in bucket "
|
|
143
|
+
f"'{trivy_s3_bucket}' with prefix '{trivy_s3_prefix}'. "
|
|
144
|
+
"Skipping Trivy sync to avoid potential data loss. "
|
|
145
|
+
"Please check the S3 bucket and prefix configuration. We expect the json files in s3 to be named "
|
|
146
|
+
f"`<image_uri>.json` and to be in the same bucket and prefix as the scan results. If the prefix is "
|
|
147
|
+
"a folder, it MUST end with a trailing slash '/'. "
|
|
148
|
+
)
|
|
149
|
+
raise ValueError("No ECR images with S3 json scan results found.")
|
|
150
|
+
|
|
151
|
+
logger.info(f"Processing {len(json_files)} Trivy result files from S3")
|
|
152
|
+
s3_client = boto3_session.client("s3")
|
|
153
|
+
for s3_object_key in json_files:
|
|
154
|
+
logger.debug(
|
|
155
|
+
f"Reading scan results from S3: s3://{trivy_s3_bucket}/{s3_object_key}"
|
|
156
|
+
)
|
|
157
|
+
response = s3_client.get_object(Bucket=trivy_s3_bucket, Key=s3_object_key)
|
|
158
|
+
scan_data_json = response["Body"].read().decode("utf-8")
|
|
159
|
+
trivy_data = json.loads(scan_data_json)
|
|
160
|
+
|
|
161
|
+
prepared = _prepare_trivy_data(
|
|
162
|
+
trivy_data,
|
|
163
|
+
image_uris=image_uris,
|
|
164
|
+
digest_aliases=digest_aliases,
|
|
165
|
+
source=f"s3://{trivy_s3_bucket}/{s3_object_key}",
|
|
166
|
+
)
|
|
167
|
+
if prepared is None:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
prepared_data, display_uri = prepared
|
|
171
|
+
sync_single_image(
|
|
172
|
+
neo4j_session,
|
|
173
|
+
prepared_data,
|
|
174
|
+
display_uri,
|
|
175
|
+
update_tag,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@timeit
|
|
182
|
+
def sync_trivy_aws_ecr_from_dir(
|
|
183
|
+
neo4j_session: Session,
|
|
184
|
+
results_dir: str,
|
|
185
|
+
update_tag: int,
|
|
186
|
+
common_job_parameters: dict[str, Any],
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Sync Trivy scan results from local files for AWS ECR images."""
|
|
189
|
+
logger.info(f"Using Trivy scan results from {results_dir}")
|
|
190
|
+
|
|
191
|
+
image_uris, digest_aliases = _get_scan_targets_and_aliases(neo4j_session)
|
|
192
|
+
json_files: set[str] = get_json_files_in_dir(results_dir)
|
|
193
|
+
|
|
194
|
+
if not json_files:
|
|
195
|
+
logger.error(
|
|
196
|
+
f"Trivy sync was configured, but no json files were found in {results_dir}."
|
|
197
|
+
)
|
|
198
|
+
raise ValueError("No Trivy json results found on disk")
|
|
199
|
+
|
|
200
|
+
logger.info(f"Processing {len(json_files)} local Trivy result files")
|
|
201
|
+
|
|
202
|
+
for file_path in json_files:
|
|
203
|
+
try:
|
|
204
|
+
with open(file_path, encoding="utf-8") as f:
|
|
205
|
+
trivy_data = json.load(f)
|
|
206
|
+
except json.JSONDecodeError as e:
|
|
207
|
+
logger.error(f"Failed to read Trivy data from {file_path}: {e}")
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
prepared = _prepare_trivy_data(
|
|
211
|
+
trivy_data,
|
|
212
|
+
image_uris=image_uris,
|
|
213
|
+
digest_aliases=digest_aliases,
|
|
214
|
+
source=file_path,
|
|
215
|
+
)
|
|
216
|
+
if prepared is None:
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
prepared_data, display_uri = prepared
|
|
220
|
+
sync_single_image(
|
|
221
|
+
neo4j_session,
|
|
222
|
+
prepared_data,
|
|
223
|
+
display_uri,
|
|
224
|
+
update_tag,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@timeit
|
|
231
|
+
def start_trivy_ingestion(neo4j_session: Session, config: Config) -> None:
|
|
232
|
+
"""Start Trivy scan ingestion from S3 or local files.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
neo4j_session: Neo4j session for database operations
|
|
236
|
+
config: Configuration object containing S3 or directory paths
|
|
237
|
+
"""
|
|
238
|
+
if not config.trivy_s3_bucket and not config.trivy_results_dir:
|
|
239
|
+
logger.info("Trivy configuration not provided. Skipping Trivy ingestion.")
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
if config.trivy_results_dir:
|
|
243
|
+
common_job_parameters = {
|
|
244
|
+
"UPDATE_TAG": config.update_tag,
|
|
245
|
+
}
|
|
246
|
+
sync_trivy_aws_ecr_from_dir(
|
|
247
|
+
neo4j_session,
|
|
248
|
+
config.trivy_results_dir,
|
|
249
|
+
config.update_tag,
|
|
250
|
+
common_job_parameters,
|
|
251
|
+
)
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
if config.trivy_s3_prefix is None:
|
|
255
|
+
config.trivy_s3_prefix = ""
|
|
256
|
+
|
|
257
|
+
common_job_parameters = {
|
|
258
|
+
"UPDATE_TAG": config.update_tag,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
boto3_session = boto3.Session()
|
|
262
|
+
|
|
263
|
+
sync_trivy_aws_ecr_from_s3(
|
|
264
|
+
neo4j_session,
|
|
265
|
+
config.trivy_s3_bucket,
|
|
266
|
+
config.trivy_s3_prefix,
|
|
267
|
+
config.update_tag,
|
|
268
|
+
common_job_parameters,
|
|
269
|
+
boto3_session,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Support other Trivy resource types here e.g. if Google Cloud has images.
|