cartography 0.102.0rc2__py3-none-any.whl → 0.103.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.
Potentially problematic release.
This version of cartography might be problematic. Click here for more details.
- cartography/__main__.py +1 -2
- cartography/_version.py +2 -2
- cartography/cli.py +376 -249
- cartography/client/core/tx.py +39 -18
- cartography/config.py +28 -0
- cartography/driftdetect/__main__.py +1 -2
- cartography/driftdetect/add_shortcut.py +10 -2
- cartography/driftdetect/cli.py +71 -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 +35 -15
- cartography/graph/job.py +46 -17
- cartography/graph/querybuilder.py +165 -80
- cartography/graph/statement.py +35 -26
- cartography/intel/analysis.py +4 -1
- cartography/intel/aws/__init__.py +114 -55
- cartography/intel/aws/apigateway.py +134 -63
- cartography/intel/aws/cloudtrail.py +127 -0
- cartography/intel/aws/cloudwatch.py +93 -0
- cartography/intel/aws/config.py +56 -20
- cartography/intel/aws/dynamodb.py +108 -40
- cartography/intel/aws/ec2/__init__.py +2 -2
- cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
- cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
- cartography/intel/aws/ec2/images.py +49 -20
- cartography/intel/aws/ec2/instances.py +234 -136
- cartography/intel/aws/ec2/internet_gateways.py +40 -11
- cartography/intel/aws/ec2/key_pairs.py +44 -20
- cartography/intel/aws/ec2/launch_templates.py +101 -59
- cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
- cartography/intel/aws/ec2/load_balancers.py +82 -42
- cartography/intel/aws/ec2/network_acls.py +89 -65
- cartography/intel/aws/ec2/network_interfaces.py +146 -87
- cartography/intel/aws/ec2/reserved_instances.py +45 -16
- cartography/intel/aws/ec2/route_tables.py +138 -98
- cartography/intel/aws/ec2/security_groups.py +71 -21
- cartography/intel/aws/ec2/snapshots.py +61 -22
- cartography/intel/aws/ec2/subnets.py +54 -18
- cartography/intel/aws/ec2/tgw.py +100 -34
- cartography/intel/aws/ec2/util.py +1 -1
- cartography/intel/aws/ec2/volumes.py +69 -41
- cartography/intel/aws/ec2/vpc.py +37 -12
- cartography/intel/aws/ec2/vpc_peerings.py +83 -24
- cartography/intel/aws/ecr.py +88 -32
- cartography/intel/aws/ecs.py +83 -47
- cartography/intel/aws/efs.py +93 -0
- cartography/intel/aws/eks.py +55 -29
- cartography/intel/aws/elasticache.py +42 -18
- cartography/intel/aws/elasticsearch.py +57 -20
- cartography/intel/aws/emr.py +61 -23
- cartography/intel/aws/iam.py +401 -145
- cartography/intel/aws/iam_instance_profiles.py +22 -22
- cartography/intel/aws/identitycenter.py +71 -37
- cartography/intel/aws/inspector.py +159 -89
- cartography/intel/aws/kms.py +92 -38
- cartography/intel/aws/lambda_function.py +103 -34
- cartography/intel/aws/organizations.py +30 -10
- cartography/intel/aws/permission_relationships.py +133 -51
- cartography/intel/aws/rds.py +249 -85
- cartography/intel/aws/redshift.py +107 -46
- cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
- cartography/intel/aws/resources.py +57 -46
- cartography/intel/aws/route53.py +108 -61
- cartography/intel/aws/s3.py +168 -83
- cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
- cartography/intel/aws/secretsmanager.py +24 -12
- cartography/intel/aws/securityhub.py +20 -9
- cartography/intel/aws/sns.py +166 -0
- cartography/intel/aws/sqs.py +60 -28
- cartography/intel/aws/ssm.py +70 -30
- cartography/intel/aws/util/arns.py +7 -7
- cartography/intel/aws/util/common.py +31 -4
- cartography/intel/azure/__init__.py +78 -19
- cartography/intel/azure/compute.py +101 -27
- cartography/intel/azure/cosmosdb.py +496 -170
- cartography/intel/azure/sql.py +296 -105
- cartography/intel/azure/storage.py +322 -113
- cartography/intel/azure/subscription.py +39 -23
- cartography/intel/azure/tenant.py +13 -4
- cartography/intel/azure/util/credentials.py +95 -55
- 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 +3 -2
- cartography/intel/crowdstrike/__init__.py +11 -9
- cartography/intel/crowdstrike/endpoints.py +5 -1
- cartography/intel/crowdstrike/spotlight.py +8 -3
- cartography/intel/cve/__init__.py +46 -13
- cartography/intel/cve/feed.py +48 -12
- 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 +36 -10
- 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 +25 -7
- cartography/intel/entra/ou.py +112 -0
- cartography/intel/entra/users.py +69 -63
- cartography/intel/gcp/__init__.py +185 -49
- cartography/intel/gcp/compute.py +418 -231
- cartography/intel/gcp/crm.py +96 -43
- cartography/intel/gcp/dns.py +60 -19
- cartography/intel/gcp/gke.py +72 -38
- cartography/intel/gcp/iam.py +61 -41
- cartography/intel/gcp/storage.py +84 -55
- cartography/intel/github/__init__.py +13 -11
- cartography/intel/github/repos.py +270 -137
- cartography/intel/github/teams.py +170 -88
- cartography/intel/github/users.py +70 -39
- cartography/intel/github/util.py +36 -34
- cartography/intel/gsuite/__init__.py +47 -26
- cartography/intel/gsuite/api.py +73 -30
- cartography/intel/jamf/__init__.py +19 -1
- cartography/intel/jamf/computers.py +30 -7
- cartography/intel/jamf/util.py +7 -2
- cartography/intel/kandji/__init__.py +6 -3
- cartography/intel/kandji/devices.py +14 -8
- cartography/intel/kubernetes/namespaces.py +7 -4
- cartography/intel/kubernetes/pods.py +7 -4
- cartography/intel/kubernetes/services.py +8 -4
- 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 +134 -38
- cartography/intel/oci/organizations.py +13 -6
- cartography/intel/oci/utils.py +43 -20
- cartography/intel/okta/__init__.py +66 -15
- cartography/intel/okta/applications.py +42 -20
- cartography/intel/okta/awssaml.py +93 -33
- cartography/intel/okta/factors.py +16 -4
- cartography/intel/okta/groups.py +56 -29
- cartography/intel/okta/organization.py +5 -1
- cartography/intel/okta/origins.py +6 -2
- cartography/intel/okta/roles.py +15 -5
- cartography/intel/okta/users.py +20 -8
- cartography/intel/okta/utils.py +6 -4
- cartography/intel/openai/__init__.py +86 -0
- cartography/intel/openai/adminapikeys.py +90 -0
- cartography/intel/openai/apikeys.py +96 -0
- cartography/intel/openai/projects.py +94 -0
- cartography/intel/openai/serviceaccounts.py +82 -0
- cartography/intel/openai/users.py +78 -0
- cartography/intel/openai/util.py +29 -0
- cartography/intel/pagerduty/__init__.py +8 -7
- cartography/intel/pagerduty/escalation_policies.py +18 -6
- cartography/intel/pagerduty/schedules.py +12 -4
- cartography/intel/pagerduty/services.py +11 -4
- cartography/intel/pagerduty/teams.py +8 -3
- cartography/intel/pagerduty/users.py +3 -1
- cartography/intel/pagerduty/vendors.py +3 -1
- cartography/intel/semgrep/__init__.py +24 -6
- cartography/intel/semgrep/dependencies.py +50 -28
- cartography/intel/semgrep/deployment.py +3 -1
- cartography/intel/semgrep/findings.py +42 -18
- cartography/intel/snipeit/__init__.py +17 -3
- cartography/intel/snipeit/asset.py +12 -6
- cartography/intel/snipeit/user.py +8 -5
- cartography/intel/snipeit/util.py +9 -4
- 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/models/aws/apigateway.py +21 -17
- cartography/models/aws/apigatewaycertificate.py +28 -22
- cartography/models/aws/apigatewayresource.py +28 -20
- cartography/models/aws/apigatewaystage.py +33 -25
- cartography/models/aws/cloudtrail/__init__.py +0 -0
- cartography/models/aws/cloudtrail/trail.py +61 -0
- cartography/models/aws/cloudwatch/__init__.py +0 -0
- cartography/models/aws/cloudwatch/loggroup.py +52 -0
- cartography/models/aws/dynamodb/gsi.py +30 -22
- cartography/models/aws/dynamodb/tables.py +25 -17
- cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
- cartography/models/aws/ec2/images.py +36 -34
- cartography/models/aws/ec2/instances.py +51 -45
- cartography/models/aws/ec2/keypair.py +21 -16
- cartography/models/aws/ec2/keypair_instance.py +28 -21
- cartography/models/aws/ec2/launch_configurations.py +30 -26
- 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 +27 -23
- cartography/models/aws/ec2/load_balancers.py +47 -37
- cartography/models/aws/ec2/network_acl_rules.py +38 -30
- cartography/models/aws/ec2/network_acls.py +38 -29
- cartography/models/aws/ec2/networkinterface_instance.py +52 -39
- cartography/models/aws/ec2/networkinterfaces.py +53 -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 +44 -34
- cartography/models/aws/ec2/route_tables.py +50 -43
- cartography/models/aws/ec2/routes.py +45 -37
- cartography/models/aws/ec2/securitygroup_instance.py +29 -20
- cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
- cartography/models/aws/ec2/subnet_instance.py +24 -19
- cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
- cartography/models/aws/ec2/volumes.py +47 -40
- cartography/models/aws/efs/__init__.py +0 -0
- cartography/models/aws/efs/mount_target.py +52 -0
- cartography/models/aws/eks/clusters.py +23 -21
- cartography/models/aws/emr.py +32 -30
- cartography/models/aws/iam/instanceprofile.py +33 -24
- cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
- cartography/models/aws/identitycenter/awspermissionset.py +37 -29
- cartography/models/aws/identitycenter/awsssouser.py +23 -21
- cartography/models/aws/inspector/findings.py +77 -65
- cartography/models/aws/inspector/packages.py +35 -29
- cartography/models/aws/s3/__init__.py +0 -0
- cartography/models/aws/s3/account_public_access_block.py +51 -0
- cartography/models/aws/sns/__init__.py +0 -0
- cartography/models/aws/sns/topic.py +50 -0
- cartography/models/aws/ssm/instance_information.py +51 -39
- cartography/models/aws/ssm/instance_patch.py +32 -26
- 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 +82 -0
- cartography/models/cloudflare/role.py +44 -0
- cartography/models/cloudflare/zone.py +59 -0
- cartography/models/core/common.py +12 -10
- cartography/models/core/nodes.py +5 -2
- cartography/models/core/relationships.py +14 -6
- cartography/models/crowdstrike/hosts.py +37 -35
- 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 +56 -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 +46 -44
- cartography/models/duo/web_authn_credential.py +27 -19
- cartography/models/entra/ou.py +48 -0
- cartography/models/entra/tenant.py +24 -18
- cartography/models/entra/user.py +64 -48
- cartography/models/gcp/iam.py +23 -23
- cartography/models/github/orgs.py +5 -4
- cartography/models/github/teams.py +37 -31
- cartography/models/github/users.py +34 -23
- cartography/models/kandji/device.py +22 -16
- cartography/models/kandji/tenant.py +6 -4
- cartography/models/lastpass/tenant.py +3 -3
- cartography/models/lastpass/user.py +32 -28
- cartography/models/openai/__init__.py +0 -0
- cartography/models/openai/adminapikey.py +90 -0
- cartography/models/openai/apikey.py +84 -0
- cartography/models/openai/organization.py +17 -0
- cartography/models/openai/project.py +70 -0
- cartography/models/openai/serviceaccount.py +50 -0
- cartography/models/openai/user.py +49 -0
- cartography/models/semgrep/dependencies.py +36 -24
- cartography/models/semgrep/deployment.py +5 -5
- cartography/models/semgrep/findings.py +58 -42
- cartography/models/semgrep/locations.py +27 -21
- cartography/models/snipeit/asset.py +30 -21
- cartography/models/snipeit/tenant.py +6 -4
- cartography/models/snipeit/user.py +19 -12
- cartography/models/tailscale/__init__.py +0 -0
- cartography/models/tailscale/device.py +95 -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 +52 -0
- cartography/stats.py +3 -3
- cartography/sync.py +113 -31
- cartography/util.py +84 -62
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
- cartography-0.103.0.dist-info/RECORD +442 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc2.dist-info/RECORD +0 -381
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
cartography/intel/entra/users.py
CHANGED
|
@@ -56,51 +56,51 @@ def transform_users(users: list[User]) -> list[dict[str, Any]]:
|
|
|
56
56
|
result: list[dict[str, Any]] = []
|
|
57
57
|
for user in users:
|
|
58
58
|
transformed_user = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
59
|
+
"id": user.id,
|
|
60
|
+
"user_principal_name": user.user_principal_name,
|
|
61
|
+
"display_name": user.display_name,
|
|
62
|
+
"given_name": user.given_name,
|
|
63
|
+
"surname": user.surname,
|
|
64
|
+
"mail": user.mail,
|
|
65
|
+
"other_mails": user.other_mails,
|
|
66
|
+
"preferred_language": user.preferred_language,
|
|
67
|
+
"preferred_name": user.preferred_name,
|
|
68
|
+
"state": user.state,
|
|
69
|
+
"usage_location": user.usage_location,
|
|
70
|
+
"user_type": user.user_type,
|
|
71
|
+
"show_in_address_list": user.show_in_address_list,
|
|
72
|
+
"sign_in_sessions_valid_from_date_time": user.sign_in_sessions_valid_from_date_time,
|
|
73
|
+
"security_identifier": user.on_premises_security_identifier,
|
|
74
|
+
"account_enabled": user.account_enabled,
|
|
75
|
+
"age_group": user.age_group,
|
|
76
|
+
"business_phones": user.business_phones,
|
|
77
|
+
"city": user.city,
|
|
78
|
+
"company_name": user.company_name,
|
|
79
|
+
"consent_provided_for_minor": user.consent_provided_for_minor,
|
|
80
|
+
"country": user.country,
|
|
81
|
+
"created_date_time": user.created_date_time,
|
|
82
|
+
"creation_type": user.creation_type,
|
|
83
|
+
"deleted_date_time": user.deleted_date_time,
|
|
84
|
+
"department": user.department,
|
|
85
|
+
"employee_id": user.employee_id,
|
|
86
|
+
"employee_type": user.employee_type,
|
|
87
|
+
"external_user_state": user.external_user_state,
|
|
88
|
+
"external_user_state_change_date_time": user.external_user_state_change_date_time,
|
|
89
|
+
"hire_date": user.hire_date,
|
|
90
|
+
"is_management_restricted": user.is_management_restricted,
|
|
91
|
+
"is_resource_account": user.is_resource_account,
|
|
92
|
+
"job_title": user.job_title,
|
|
93
|
+
"last_password_change_date_time": user.last_password_change_date_time,
|
|
94
|
+
"mail_nickname": user.mail_nickname,
|
|
95
|
+
"office_location": user.office_location,
|
|
96
|
+
"on_premises_distinguished_name": user.on_premises_distinguished_name,
|
|
97
|
+
"on_premises_domain_name": user.on_premises_domain_name,
|
|
98
|
+
"on_premises_immutable_id": user.on_premises_immutable_id,
|
|
99
|
+
"on_premises_last_sync_date_time": user.on_premises_last_sync_date_time,
|
|
100
|
+
"on_premises_sam_account_name": user.on_premises_sam_account_name,
|
|
101
|
+
"on_premises_security_identifier": user.on_premises_security_identifier,
|
|
102
|
+
"on_premises_sync_enabled": user.on_premises_sync_enabled,
|
|
103
|
+
"on_premises_user_principal_name": user.on_premises_user_principal_name,
|
|
104
104
|
}
|
|
105
105
|
result.append(transformed_user)
|
|
106
106
|
return result
|
|
@@ -112,21 +112,21 @@ def transform_tenant(tenant: Organization, tenant_id: str) -> dict[str, Any]:
|
|
|
112
112
|
Transform the tenant data into the format expected by our schema
|
|
113
113
|
"""
|
|
114
114
|
return {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
"id": tenant_id,
|
|
116
|
+
"created_date_time": tenant.created_date_time,
|
|
117
|
+
"default_usage_location": tenant.default_usage_location,
|
|
118
|
+
"deleted_date_time": tenant.deleted_date_time,
|
|
119
|
+
"display_name": tenant.display_name,
|
|
120
|
+
"marketing_notification_emails": tenant.marketing_notification_emails,
|
|
121
|
+
"mobile_device_management_authority": tenant.mobile_device_management_authority,
|
|
122
|
+
"on_premises_last_sync_date_time": tenant.on_premises_last_sync_date_time,
|
|
123
|
+
"on_premises_sync_enabled": tenant.on_premises_sync_enabled,
|
|
124
|
+
"partner_tenant_type": tenant.partner_tenant_type,
|
|
125
|
+
"postal_code": tenant.postal_code,
|
|
126
|
+
"preferred_language": tenant.preferred_language,
|
|
127
|
+
"state": tenant.state,
|
|
128
|
+
"street": tenant.street,
|
|
129
|
+
"tenant_type": tenant.tenant_type,
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
|
|
@@ -161,8 +161,12 @@ def load_users(
|
|
|
161
161
|
)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
def cleanup(
|
|
165
|
-
|
|
164
|
+
def cleanup(
|
|
165
|
+
neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
|
|
166
|
+
) -> None:
|
|
167
|
+
GraphJob.from_node_schema(EntraUserSchema(), common_job_parameters).run(
|
|
168
|
+
neo4j_session
|
|
169
|
+
)
|
|
166
170
|
|
|
167
171
|
|
|
168
172
|
@timeit
|
|
@@ -190,7 +194,9 @@ async def sync_entra_users(
|
|
|
190
194
|
client_id=client_id,
|
|
191
195
|
client_secret=client_secret,
|
|
192
196
|
)
|
|
193
|
-
client = GraphServiceClient(
|
|
197
|
+
client = GraphServiceClient(
|
|
198
|
+
credential, scopes=["https://graph.microsoft.com/.default"]
|
|
199
|
+
)
|
|
194
200
|
|
|
195
201
|
# Get tenant information
|
|
196
202
|
tenant = await get_tenant(client)
|
|
@@ -24,17 +24,19 @@ from cartography.util import run_analysis_job
|
|
|
24
24
|
from cartography.util import timeit
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
|
-
Resources = namedtuple(
|
|
27
|
+
Resources = namedtuple(
|
|
28
|
+
"Resources", "compute container crm_v1 crm_v2 dns storage serviceusage iam"
|
|
29
|
+
)
|
|
28
30
|
|
|
29
31
|
# Mapping of service short names to their full names as in docs. See https://developers.google.com/apis-explorer,
|
|
30
32
|
# and https://cloud.google.com/service-usage/docs/reference/rest/v1/services#ServiceConfig
|
|
31
|
-
Services = namedtuple(
|
|
33
|
+
Services = namedtuple("Services", "compute storage gke dns iam")
|
|
32
34
|
service_names = Services(
|
|
33
|
-
compute=
|
|
34
|
-
storage=
|
|
35
|
-
gke=
|
|
36
|
-
dns=
|
|
37
|
-
iam=
|
|
35
|
+
compute="compute.googleapis.com",
|
|
36
|
+
storage="storage.googleapis.com",
|
|
37
|
+
gke="container.googleapis.com",
|
|
38
|
+
dns="dns.googleapis.com",
|
|
39
|
+
iam="iam.googleapis.com",
|
|
38
40
|
)
|
|
39
41
|
|
|
40
42
|
|
|
@@ -47,7 +49,12 @@ def _get_crm_resource_v1(credentials: GoogleCredentials) -> Resource:
|
|
|
47
49
|
"""
|
|
48
50
|
# cache_discovery=False to suppress extra warnings.
|
|
49
51
|
# See https://github.com/googleapis/google-api-python-client/issues/299#issuecomment-268915510 and related issues
|
|
50
|
-
return googleapiclient.discovery.build(
|
|
52
|
+
return googleapiclient.discovery.build(
|
|
53
|
+
"cloudresourcemanager",
|
|
54
|
+
"v1",
|
|
55
|
+
credentials=credentials,
|
|
56
|
+
cache_discovery=False,
|
|
57
|
+
)
|
|
51
58
|
|
|
52
59
|
|
|
53
60
|
def _get_crm_resource_v2(credentials: GoogleCredentials) -> Resource:
|
|
@@ -57,7 +64,12 @@ def _get_crm_resource_v2(credentials: GoogleCredentials) -> Resource:
|
|
|
57
64
|
:param credentials: The GoogleCredentials object
|
|
58
65
|
:return: A CRM v2 resource object
|
|
59
66
|
"""
|
|
60
|
-
return googleapiclient.discovery.build(
|
|
67
|
+
return googleapiclient.discovery.build(
|
|
68
|
+
"cloudresourcemanager",
|
|
69
|
+
"v2",
|
|
70
|
+
credentials=credentials,
|
|
71
|
+
cache_discovery=False,
|
|
72
|
+
)
|
|
61
73
|
|
|
62
74
|
|
|
63
75
|
def _get_compute_resource(credentials: GoogleCredentials) -> Resource:
|
|
@@ -67,7 +79,12 @@ def _get_compute_resource(credentials: GoogleCredentials) -> Resource:
|
|
|
67
79
|
:param credentials: The GoogleCredentials object
|
|
68
80
|
:return: A Compute resource object
|
|
69
81
|
"""
|
|
70
|
-
return googleapiclient.discovery.build(
|
|
82
|
+
return googleapiclient.discovery.build(
|
|
83
|
+
"compute",
|
|
84
|
+
"v1",
|
|
85
|
+
credentials=credentials,
|
|
86
|
+
cache_discovery=False,
|
|
87
|
+
)
|
|
71
88
|
|
|
72
89
|
|
|
73
90
|
def _get_storage_resource(credentials: GoogleCredentials) -> Resource:
|
|
@@ -79,7 +96,12 @@ def _get_storage_resource(credentials: GoogleCredentials) -> Resource:
|
|
|
79
96
|
:param credentials: The GoogleCredentials object
|
|
80
97
|
:return: A Storage resource object
|
|
81
98
|
"""
|
|
82
|
-
return googleapiclient.discovery.build(
|
|
99
|
+
return googleapiclient.discovery.build(
|
|
100
|
+
"storage",
|
|
101
|
+
"v1",
|
|
102
|
+
credentials=credentials,
|
|
103
|
+
cache_discovery=False,
|
|
104
|
+
)
|
|
83
105
|
|
|
84
106
|
|
|
85
107
|
def _get_container_resource(credentials: GoogleCredentials) -> Resource:
|
|
@@ -90,7 +112,12 @@ def _get_container_resource(credentials: GoogleCredentials) -> Resource:
|
|
|
90
112
|
:param credentials: The GoogleCredentials object
|
|
91
113
|
:return: A Container resource object
|
|
92
114
|
"""
|
|
93
|
-
return googleapiclient.discovery.build(
|
|
115
|
+
return googleapiclient.discovery.build(
|
|
116
|
+
"container",
|
|
117
|
+
"v1",
|
|
118
|
+
credentials=credentials,
|
|
119
|
+
cache_discovery=False,
|
|
120
|
+
)
|
|
94
121
|
|
|
95
122
|
|
|
96
123
|
def _get_dns_resource(credentials: GoogleCredentials) -> Resource:
|
|
@@ -101,7 +128,12 @@ def _get_dns_resource(credentials: GoogleCredentials) -> Resource:
|
|
|
101
128
|
:param credentials: The GoogleCredentials object
|
|
102
129
|
:return: A DNS resource object
|
|
103
130
|
"""
|
|
104
|
-
return googleapiclient.discovery.build(
|
|
131
|
+
return googleapiclient.discovery.build(
|
|
132
|
+
"dns",
|
|
133
|
+
"v1",
|
|
134
|
+
credentials=credentials,
|
|
135
|
+
cache_discovery=False,
|
|
136
|
+
)
|
|
105
137
|
|
|
106
138
|
|
|
107
139
|
def _get_serviceusage_resource(credentials: GoogleCredentials) -> Resource:
|
|
@@ -112,14 +144,21 @@ def _get_serviceusage_resource(credentials: GoogleCredentials) -> Resource:
|
|
|
112
144
|
:param credentials: The GoogleCredentials object
|
|
113
145
|
:return: A serviceusage resource object
|
|
114
146
|
"""
|
|
115
|
-
return googleapiclient.discovery.build(
|
|
147
|
+
return googleapiclient.discovery.build(
|
|
148
|
+
"serviceusage",
|
|
149
|
+
"v1",
|
|
150
|
+
credentials=credentials,
|
|
151
|
+
cache_discovery=False,
|
|
152
|
+
)
|
|
116
153
|
|
|
117
154
|
|
|
118
155
|
def _get_iam_resource(credentials: GoogleCredentials) -> Resource:
|
|
119
156
|
"""
|
|
120
157
|
Instantiates a Google IAM resource object to call the IAM API.
|
|
121
158
|
"""
|
|
122
|
-
return googleapiclient.discovery.build(
|
|
159
|
+
return googleapiclient.discovery.build(
|
|
160
|
+
"iam", "v1", credentials=credentials, cache_discovery=False
|
|
161
|
+
)
|
|
123
162
|
|
|
124
163
|
|
|
125
164
|
def _initialize_resources(credentials: GoogleCredentials) -> Resource:
|
|
@@ -150,16 +189,22 @@ def _services_enabled_on_project(serviceusage: Resource, project_id: str) -> Set
|
|
|
150
189
|
:return: A set of services that are enabled on the project
|
|
151
190
|
"""
|
|
152
191
|
try:
|
|
153
|
-
req = serviceusage.services().list(
|
|
192
|
+
req = serviceusage.services().list(
|
|
193
|
+
parent=f"projects/{project_id}",
|
|
194
|
+
filter="state:ENABLED",
|
|
195
|
+
)
|
|
154
196
|
services = set()
|
|
155
197
|
while req is not None:
|
|
156
198
|
res = req.execute()
|
|
157
|
-
if
|
|
158
|
-
services.update({svc[
|
|
159
|
-
req = serviceusage.services().list_next(
|
|
199
|
+
if "services" in res:
|
|
200
|
+
services.update({svc["config"]["name"] for svc in res["services"]})
|
|
201
|
+
req = serviceusage.services().list_next(
|
|
202
|
+
previous_request=req,
|
|
203
|
+
previous_response=res,
|
|
204
|
+
)
|
|
160
205
|
return services
|
|
161
206
|
except googleapiclient.discovery.HttpError as http_error:
|
|
162
|
-
http_error = json.loads(http_error.content.decode(
|
|
207
|
+
http_error = json.loads(http_error.content.decode("utf-8"))
|
|
163
208
|
# This is set to log-level `info` because Google creates many projects under the hood that cartography cannot
|
|
164
209
|
# audit (e.g. adding a script to a Google spreadsheet causes a project to get created) and we don't need to emit
|
|
165
210
|
# a warning for these projects.
|
|
@@ -172,7 +217,10 @@ def _services_enabled_on_project(serviceusage: Resource, project_id: str) -> Set
|
|
|
172
217
|
|
|
173
218
|
|
|
174
219
|
def _sync_single_project_compute(
|
|
175
|
-
neo4j_session: neo4j.Session,
|
|
220
|
+
neo4j_session: neo4j.Session,
|
|
221
|
+
resources: Resource,
|
|
222
|
+
project_id: str,
|
|
223
|
+
gcp_update_tag: int,
|
|
176
224
|
common_job_parameters: Dict,
|
|
177
225
|
) -> None:
|
|
178
226
|
"""
|
|
@@ -189,11 +237,20 @@ def _sync_single_project_compute(
|
|
|
189
237
|
enabled_services = _services_enabled_on_project(resources.serviceusage, project_id)
|
|
190
238
|
compute_cred = _get_compute_resource(get_gcp_credentials())
|
|
191
239
|
if service_names.compute in enabled_services:
|
|
192
|
-
compute.sync(
|
|
240
|
+
compute.sync(
|
|
241
|
+
neo4j_session,
|
|
242
|
+
compute_cred,
|
|
243
|
+
project_id,
|
|
244
|
+
gcp_update_tag,
|
|
245
|
+
common_job_parameters,
|
|
246
|
+
)
|
|
193
247
|
|
|
194
248
|
|
|
195
249
|
def _sync_single_project_storage(
|
|
196
|
-
neo4j_session: neo4j.Session,
|
|
250
|
+
neo4j_session: neo4j.Session,
|
|
251
|
+
resources: Resource,
|
|
252
|
+
project_id: str,
|
|
253
|
+
gcp_update_tag: int,
|
|
197
254
|
common_job_parameters: Dict,
|
|
198
255
|
) -> None:
|
|
199
256
|
"""
|
|
@@ -210,11 +267,20 @@ def _sync_single_project_storage(
|
|
|
210
267
|
enabled_services = _services_enabled_on_project(resources.serviceusage, project_id)
|
|
211
268
|
storage_cred = _get_storage_resource(get_gcp_credentials())
|
|
212
269
|
if service_names.storage in enabled_services:
|
|
213
|
-
storage.sync_gcp_buckets(
|
|
270
|
+
storage.sync_gcp_buckets(
|
|
271
|
+
neo4j_session,
|
|
272
|
+
storage_cred,
|
|
273
|
+
project_id,
|
|
274
|
+
gcp_update_tag,
|
|
275
|
+
common_job_parameters,
|
|
276
|
+
)
|
|
214
277
|
|
|
215
278
|
|
|
216
279
|
def _sync_single_project_gke(
|
|
217
|
-
neo4j_session: neo4j.Session,
|
|
280
|
+
neo4j_session: neo4j.Session,
|
|
281
|
+
resources: Resource,
|
|
282
|
+
project_id: str,
|
|
283
|
+
gcp_update_tag: int,
|
|
218
284
|
common_job_parameters: Dict,
|
|
219
285
|
) -> None:
|
|
220
286
|
"""
|
|
@@ -231,11 +297,20 @@ def _sync_single_project_gke(
|
|
|
231
297
|
enabled_services = _services_enabled_on_project(resources.serviceusage, project_id)
|
|
232
298
|
container_cred = _get_container_resource(get_gcp_credentials())
|
|
233
299
|
if service_names.gke in enabled_services:
|
|
234
|
-
gke.sync_gke_clusters(
|
|
300
|
+
gke.sync_gke_clusters(
|
|
301
|
+
neo4j_session,
|
|
302
|
+
container_cred,
|
|
303
|
+
project_id,
|
|
304
|
+
gcp_update_tag,
|
|
305
|
+
common_job_parameters,
|
|
306
|
+
)
|
|
235
307
|
|
|
236
308
|
|
|
237
309
|
def _sync_single_project_dns(
|
|
238
|
-
neo4j_session: neo4j.Session,
|
|
310
|
+
neo4j_session: neo4j.Session,
|
|
311
|
+
resources: Resource,
|
|
312
|
+
project_id: str,
|
|
313
|
+
gcp_update_tag: int,
|
|
239
314
|
common_job_parameters: Dict,
|
|
240
315
|
) -> None:
|
|
241
316
|
"""
|
|
@@ -252,7 +327,13 @@ def _sync_single_project_dns(
|
|
|
252
327
|
enabled_services = _services_enabled_on_project(resources.serviceusage, project_id)
|
|
253
328
|
dns_cred = _get_dns_resource(get_gcp_credentials())
|
|
254
329
|
if service_names.dns in enabled_services:
|
|
255
|
-
dns.sync(
|
|
330
|
+
dns.sync(
|
|
331
|
+
neo4j_session,
|
|
332
|
+
dns_cred,
|
|
333
|
+
project_id,
|
|
334
|
+
gcp_update_tag,
|
|
335
|
+
common_job_parameters,
|
|
336
|
+
)
|
|
256
337
|
|
|
257
338
|
|
|
258
339
|
def _sync_single_project_iam(
|
|
@@ -276,12 +357,17 @@ def _sync_single_project_iam(
|
|
|
276
357
|
enabled_services = _services_enabled_on_project(resources.serviceusage, project_id)
|
|
277
358
|
iam_cred = _get_iam_resource(get_gcp_credentials())
|
|
278
359
|
if service_names.iam in enabled_services:
|
|
279
|
-
iam.sync(
|
|
360
|
+
iam.sync(
|
|
361
|
+
neo4j_session, iam_cred, project_id, gcp_update_tag, common_job_parameters
|
|
362
|
+
)
|
|
280
363
|
|
|
281
364
|
|
|
282
365
|
def _sync_multiple_projects(
|
|
283
|
-
neo4j_session: neo4j.Session,
|
|
284
|
-
|
|
366
|
+
neo4j_session: neo4j.Session,
|
|
367
|
+
resources: Resource,
|
|
368
|
+
projects: List[Dict],
|
|
369
|
+
gcp_update_tag: int,
|
|
370
|
+
common_job_parameters: Dict,
|
|
285
371
|
) -> None:
|
|
286
372
|
"""
|
|
287
373
|
Handles graph sync for multiple GCP projects.
|
|
@@ -296,36 +382,67 @@ def _sync_multiple_projects(
|
|
|
296
382
|
:return: Nothing
|
|
297
383
|
"""
|
|
298
384
|
logger.info("Syncing %d GCP projects.", len(projects))
|
|
299
|
-
crm.sync_gcp_projects(
|
|
385
|
+
crm.sync_gcp_projects(
|
|
386
|
+
neo4j_session,
|
|
387
|
+
projects,
|
|
388
|
+
gcp_update_tag,
|
|
389
|
+
common_job_parameters,
|
|
390
|
+
)
|
|
300
391
|
# Compute data sync
|
|
301
392
|
for project in projects:
|
|
302
|
-
project_id = project[
|
|
393
|
+
project_id = project["projectId"]
|
|
303
394
|
logger.info("Syncing GCP project %s for Compute.", project_id)
|
|
304
|
-
_sync_single_project_compute(
|
|
395
|
+
_sync_single_project_compute(
|
|
396
|
+
neo4j_session,
|
|
397
|
+
resources,
|
|
398
|
+
project_id,
|
|
399
|
+
gcp_update_tag,
|
|
400
|
+
common_job_parameters,
|
|
401
|
+
)
|
|
305
402
|
|
|
306
403
|
# Storage data sync
|
|
307
404
|
for project in projects:
|
|
308
|
-
project_id = project[
|
|
405
|
+
project_id = project["projectId"]
|
|
309
406
|
logger.info("Syncing GCP project %s for Storage", project_id)
|
|
310
|
-
_sync_single_project_storage(
|
|
407
|
+
_sync_single_project_storage(
|
|
408
|
+
neo4j_session,
|
|
409
|
+
resources,
|
|
410
|
+
project_id,
|
|
411
|
+
gcp_update_tag,
|
|
412
|
+
common_job_parameters,
|
|
413
|
+
)
|
|
311
414
|
|
|
312
415
|
# GKE data sync
|
|
313
416
|
for project in projects:
|
|
314
|
-
project_id = project[
|
|
417
|
+
project_id = project["projectId"]
|
|
315
418
|
logger.info("Syncing GCP project %s for GKE", project_id)
|
|
316
|
-
_sync_single_project_gke(
|
|
419
|
+
_sync_single_project_gke(
|
|
420
|
+
neo4j_session,
|
|
421
|
+
resources,
|
|
422
|
+
project_id,
|
|
423
|
+
gcp_update_tag,
|
|
424
|
+
common_job_parameters,
|
|
425
|
+
)
|
|
317
426
|
|
|
318
427
|
# DNS data sync
|
|
319
428
|
for project in projects:
|
|
320
|
-
project_id = project[
|
|
429
|
+
project_id = project["projectId"]
|
|
321
430
|
logger.info("Syncing GCP project %s for DNS", project_id)
|
|
322
|
-
_sync_single_project_dns(
|
|
431
|
+
_sync_single_project_dns(
|
|
432
|
+
neo4j_session,
|
|
433
|
+
resources,
|
|
434
|
+
project_id,
|
|
435
|
+
gcp_update_tag,
|
|
436
|
+
common_job_parameters,
|
|
437
|
+
)
|
|
323
438
|
|
|
324
439
|
# IAM data sync
|
|
325
440
|
for project in projects:
|
|
326
|
-
project_id = project[
|
|
441
|
+
project_id = project["projectId"]
|
|
327
442
|
logger.info("Syncing GCP project %s for IAM", project_id)
|
|
328
|
-
_sync_single_project_iam(
|
|
443
|
+
_sync_single_project_iam(
|
|
444
|
+
neo4j_session, resources, project_id, gcp_update_tag, common_job_parameters
|
|
445
|
+
)
|
|
329
446
|
|
|
330
447
|
|
|
331
448
|
@timeit
|
|
@@ -341,7 +458,10 @@ def get_gcp_credentials() -> Optional[GoogleCredentials]:
|
|
|
341
458
|
credentials, project_id = default()
|
|
342
459
|
return credentials
|
|
343
460
|
except DefaultCredentialsError as e:
|
|
344
|
-
logger.debug(
|
|
461
|
+
logger.debug(
|
|
462
|
+
"Error occurred calling GoogleCredentials.get_application_default().",
|
|
463
|
+
exc_info=True,
|
|
464
|
+
)
|
|
345
465
|
logger.error(
|
|
346
466
|
(
|
|
347
467
|
"Unable to initialize Google Compute Platform creds. If you don't have GCP data or don't want to load "
|
|
@@ -376,27 +496,43 @@ def start_gcp_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
376
496
|
resources = _initialize_resources(credentials)
|
|
377
497
|
|
|
378
498
|
# If we don't have perms to pull Orgs or Folders from GCP, we will skip safely
|
|
379
|
-
crm.sync_gcp_organizations(
|
|
380
|
-
|
|
499
|
+
crm.sync_gcp_organizations(
|
|
500
|
+
neo4j_session,
|
|
501
|
+
resources.crm_v1,
|
|
502
|
+
config.update_tag,
|
|
503
|
+
common_job_parameters,
|
|
504
|
+
)
|
|
505
|
+
crm.sync_gcp_folders(
|
|
506
|
+
neo4j_session,
|
|
507
|
+
resources.crm_v2,
|
|
508
|
+
config.update_tag,
|
|
509
|
+
common_job_parameters,
|
|
510
|
+
)
|
|
381
511
|
|
|
382
512
|
projects = crm.get_gcp_projects(resources.crm_v1)
|
|
383
513
|
|
|
384
|
-
_sync_multiple_projects(
|
|
514
|
+
_sync_multiple_projects(
|
|
515
|
+
neo4j_session,
|
|
516
|
+
resources,
|
|
517
|
+
projects,
|
|
518
|
+
config.update_tag,
|
|
519
|
+
common_job_parameters,
|
|
520
|
+
)
|
|
385
521
|
|
|
386
522
|
run_analysis_job(
|
|
387
|
-
|
|
523
|
+
"gcp_compute_asset_inet_exposure.json",
|
|
388
524
|
neo4j_session,
|
|
389
525
|
common_job_parameters,
|
|
390
526
|
)
|
|
391
527
|
|
|
392
528
|
run_analysis_job(
|
|
393
|
-
|
|
529
|
+
"gcp_gke_asset_exposure.json",
|
|
394
530
|
neo4j_session,
|
|
395
531
|
common_job_parameters,
|
|
396
532
|
)
|
|
397
533
|
|
|
398
534
|
run_analysis_job(
|
|
399
|
-
|
|
535
|
+
"gcp_gke_basic_auth.json",
|
|
400
536
|
neo4j_session,
|
|
401
537
|
common_job_parameters,
|
|
402
538
|
)
|