cartography 0.102.0rc1__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 +327 -0
- 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 -44
- 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 +97 -0
- cartography/models/aws/ec2/route_tables.py +128 -0
- cartography/models/aws/ec2/routes.py +85 -0
- 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.0rc1.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
- cartography-0.103.0.dist-info/RECORD +442 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc1.dist-info/RECORD +0 -377
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
|
@@ -15,7 +15,9 @@ from policyuniverse.policy import Policy
|
|
|
15
15
|
from cartography.client.core.tx import load
|
|
16
16
|
from cartography.graph.job import GraphJob
|
|
17
17
|
from cartography.models.aws.apigateway import APIGatewayRestAPISchema
|
|
18
|
-
from cartography.models.aws.apigatewaycertificate import
|
|
18
|
+
from cartography.models.aws.apigatewaycertificate import (
|
|
19
|
+
APIGatewayClientCertificateSchema,
|
|
20
|
+
)
|
|
19
21
|
from cartography.models.aws.apigatewayresource import APIGatewayResourceSchema
|
|
20
22
|
from cartography.models.aws.apigatewaystage import APIGatewayStageSchema
|
|
21
23
|
from cartography.util import aws_handle_regions
|
|
@@ -26,24 +28,29 @@ logger = logging.getLogger(__name__)
|
|
|
26
28
|
|
|
27
29
|
@timeit
|
|
28
30
|
@aws_handle_regions
|
|
29
|
-
def get_apigateway_rest_apis(
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
def get_apigateway_rest_apis(
|
|
32
|
+
boto3_session: boto3.session.Session,
|
|
33
|
+
region: str,
|
|
34
|
+
) -> List[Dict]:
|
|
35
|
+
client = boto3_session.client("apigateway", region_name=region)
|
|
36
|
+
paginator = client.get_paginator("get_rest_apis")
|
|
32
37
|
apis: List[Any] = []
|
|
33
38
|
for page in paginator.paginate():
|
|
34
|
-
apis.extend(page[
|
|
39
|
+
apis.extend(page["items"])
|
|
35
40
|
return apis
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
@timeit
|
|
39
44
|
@aws_handle_regions
|
|
40
45
|
def get_rest_api_details(
|
|
41
|
-
|
|
46
|
+
boto3_session: boto3.session.Session,
|
|
47
|
+
rest_apis: List[Dict],
|
|
48
|
+
region: str,
|
|
42
49
|
) -> List[Tuple[Any, Any, Any, Any, Any]]:
|
|
43
50
|
"""
|
|
44
51
|
Iterates over all API Gateway REST APIs.
|
|
45
52
|
"""
|
|
46
|
-
client = boto3_session.client(
|
|
53
|
+
client = boto3_session.client("apigateway", region_name=region)
|
|
47
54
|
apis = []
|
|
48
55
|
for api in rest_apis:
|
|
49
56
|
stages = get_rest_api_stages(api, client)
|
|
@@ -51,7 +58,7 @@ def get_rest_api_details(
|
|
|
51
58
|
certificate = get_rest_api_client_certificate(stages, client)
|
|
52
59
|
resources = get_rest_api_resources(api, client)
|
|
53
60
|
policy = get_rest_api_policy(api, client)
|
|
54
|
-
apis.append((api[
|
|
61
|
+
apis.append((api["id"], stages, certificate, resources, policy))
|
|
55
62
|
return apis
|
|
56
63
|
|
|
57
64
|
|
|
@@ -61,27 +68,34 @@ def get_rest_api_stages(api: Dict, client: botocore.client.BaseClient) -> Any:
|
|
|
61
68
|
Gets the REST API Stage Resources.
|
|
62
69
|
"""
|
|
63
70
|
try:
|
|
64
|
-
stages = client.get_stages(restApiId=api[
|
|
71
|
+
stages = client.get_stages(restApiId=api["id"])
|
|
65
72
|
except ClientError as e:
|
|
66
73
|
logger.warning(f'Failed to retrieve Stages for Api Id - {api["id"]} - {e}')
|
|
67
74
|
raise
|
|
68
75
|
|
|
69
|
-
return stages[
|
|
76
|
+
return stages["item"]
|
|
70
77
|
|
|
71
78
|
|
|
72
79
|
@timeit
|
|
73
|
-
def get_rest_api_client_certificate(
|
|
80
|
+
def get_rest_api_client_certificate(
|
|
81
|
+
stages: Dict,
|
|
82
|
+
client: botocore.client.BaseClient,
|
|
83
|
+
) -> Optional[Any]:
|
|
74
84
|
"""
|
|
75
85
|
Gets the current ClientCertificate resource if present, else returns None.
|
|
76
86
|
"""
|
|
77
87
|
response = None
|
|
78
88
|
for stage in stages:
|
|
79
|
-
if
|
|
89
|
+
if "clientCertificateId" in stage:
|
|
80
90
|
try:
|
|
81
|
-
response = client.get_client_certificate(
|
|
82
|
-
|
|
91
|
+
response = client.get_client_certificate(
|
|
92
|
+
clientCertificateId=stage["clientCertificateId"],
|
|
93
|
+
)
|
|
94
|
+
response["stageName"] = stage["stageName"]
|
|
83
95
|
except ClientError as e:
|
|
84
|
-
logger.warning(
|
|
96
|
+
logger.warning(
|
|
97
|
+
f"Failed to retrieve Client Certificate for Stage {stage['stageName']} - {e}",
|
|
98
|
+
)
|
|
85
99
|
raise
|
|
86
100
|
else:
|
|
87
101
|
return []
|
|
@@ -95,10 +109,10 @@ def get_rest_api_resources(api: Dict, client: botocore.client.BaseClient) -> Lis
|
|
|
95
109
|
Gets the collection of Resource resources.
|
|
96
110
|
"""
|
|
97
111
|
resources: List[Any] = []
|
|
98
|
-
paginator = client.get_paginator(
|
|
99
|
-
response_iterator = paginator.paginate(restApiId=api[
|
|
112
|
+
paginator = client.get_paginator("get_resources")
|
|
113
|
+
response_iterator = paginator.paginate(restApiId=api["id"])
|
|
100
114
|
for page in response_iterator:
|
|
101
|
-
resources.extend(page[
|
|
115
|
+
resources.extend(page["items"])
|
|
102
116
|
|
|
103
117
|
return resources
|
|
104
118
|
|
|
@@ -108,34 +122,35 @@ def get_rest_api_policy(api: Dict, client: botocore.client.BaseClient) -> Any:
|
|
|
108
122
|
"""
|
|
109
123
|
Gets the REST API policy. Returns policy string or None if no policy is present.
|
|
110
124
|
"""
|
|
111
|
-
policy = api[
|
|
125
|
+
policy = api["policy"] if "policy" in api and api["policy"] else None
|
|
112
126
|
return policy
|
|
113
127
|
|
|
114
128
|
|
|
115
129
|
def transform_apigateway_rest_apis(
|
|
116
|
-
rest_apis: List[Dict],
|
|
130
|
+
rest_apis: List[Dict],
|
|
131
|
+
resource_policies: List[Dict],
|
|
132
|
+
region: str,
|
|
133
|
+
current_aws_account_id: str,
|
|
134
|
+
aws_update_tag: int,
|
|
117
135
|
) -> List[Dict]:
|
|
118
136
|
"""
|
|
119
137
|
Transform API Gateway REST API data for ingestion, including policy analysis
|
|
120
138
|
"""
|
|
121
139
|
# Create a mapping of api_id to policy data for easier lookup
|
|
122
|
-
policy_map = {
|
|
123
|
-
policy['api_id']: policy
|
|
124
|
-
for policy in resource_policies
|
|
125
|
-
}
|
|
140
|
+
policy_map = {policy["api_id"]: policy for policy in resource_policies}
|
|
126
141
|
|
|
127
142
|
transformed_apis = []
|
|
128
143
|
for api in rest_apis:
|
|
129
|
-
policy_data = policy_map.get(api[
|
|
144
|
+
policy_data = policy_map.get(api["id"], {})
|
|
130
145
|
transformed_api = {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
"id": api["id"],
|
|
147
|
+
"createdDate": str(api["createdDate"]) if "createdDate" in api else None,
|
|
148
|
+
"version": api.get("version"),
|
|
149
|
+
"minimumCompressionSize": api.get("minimumCompressionSize"),
|
|
150
|
+
"disableExecuteApiEndpoint": api.get("disableExecuteApiEndpoint"),
|
|
136
151
|
# Set defaults in the transform function
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
"anonymous_access": policy_data.get("internet_accessible", False),
|
|
153
|
+
"anonymous_actions": policy_data.get("accessible_actions", []),
|
|
139
154
|
# TODO Issue #1452: clarify internet exposure vs anonymous access
|
|
140
155
|
}
|
|
141
156
|
transformed_apis.append(transformed_api)
|
|
@@ -145,7 +160,10 @@ def transform_apigateway_rest_apis(
|
|
|
145
160
|
|
|
146
161
|
@timeit
|
|
147
162
|
def load_apigateway_rest_apis(
|
|
148
|
-
neo4j_session: neo4j.Session,
|
|
163
|
+
neo4j_session: neo4j.Session,
|
|
164
|
+
data: List[Dict],
|
|
165
|
+
region: str,
|
|
166
|
+
current_aws_account_id: str,
|
|
149
167
|
aws_update_tag: int,
|
|
150
168
|
) -> None:
|
|
151
169
|
"""
|
|
@@ -167,21 +185,26 @@ def transform_apigateway_stages(stages: List[Dict], update_tag: int) -> List[Dic
|
|
|
167
185
|
"""
|
|
168
186
|
stage_data = []
|
|
169
187
|
for stage in stages:
|
|
170
|
-
stage[
|
|
171
|
-
stage[
|
|
188
|
+
stage["createdDate"] = str(stage["createdDate"])
|
|
189
|
+
stage["arn"] = f"arn:aws:apigateway:::{stage['apiId']}/{stage['stageName']}"
|
|
172
190
|
stage_data.append(stage)
|
|
173
191
|
return stage_data
|
|
174
192
|
|
|
175
193
|
|
|
176
|
-
def transform_apigateway_certificates(
|
|
194
|
+
def transform_apigateway_certificates(
|
|
195
|
+
certificates: List[Dict],
|
|
196
|
+
update_tag: int,
|
|
197
|
+
) -> List[Dict]:
|
|
177
198
|
"""
|
|
178
199
|
Transform API Gateway Client Certificate data for ingestion
|
|
179
200
|
"""
|
|
180
201
|
cert_data = []
|
|
181
202
|
for certificate in certificates:
|
|
182
|
-
certificate[
|
|
183
|
-
certificate[
|
|
184
|
-
certificate[
|
|
203
|
+
certificate["createdDate"] = str(certificate["createdDate"])
|
|
204
|
+
certificate["expirationDate"] = str(certificate.get("expirationDate"))
|
|
205
|
+
certificate["stageArn"] = (
|
|
206
|
+
f"arn:aws:apigateway:::{certificate['apiId']}/{certificate['stageName']}"
|
|
207
|
+
)
|
|
185
208
|
cert_data.append(certificate)
|
|
186
209
|
return cert_data
|
|
187
210
|
|
|
@@ -199,21 +222,23 @@ def transform_rest_api_details(
|
|
|
199
222
|
for api_id, stage, certificate, resource, _ in stages_certificate_resources:
|
|
200
223
|
if len(stage) > 0:
|
|
201
224
|
for s in stage:
|
|
202
|
-
s[
|
|
203
|
-
s[
|
|
204
|
-
s[
|
|
225
|
+
s["apiId"] = api_id
|
|
226
|
+
s["createdDate"] = str(s["createdDate"])
|
|
227
|
+
s["arn"] = f"arn:aws:apigateway:::{api_id}/{s['stageName']}"
|
|
205
228
|
stages.extend(stage)
|
|
206
229
|
|
|
207
230
|
if certificate:
|
|
208
|
-
certificate[
|
|
209
|
-
certificate[
|
|
210
|
-
certificate[
|
|
211
|
-
certificate[
|
|
231
|
+
certificate["apiId"] = api_id
|
|
232
|
+
certificate["createdDate"] = str(certificate["createdDate"])
|
|
233
|
+
certificate["expirationDate"] = str(certificate.get("expirationDate"))
|
|
234
|
+
certificate["stageArn"] = (
|
|
235
|
+
f"arn:aws:apigateway:::{api_id}/{certificate['stageName']}"
|
|
236
|
+
)
|
|
212
237
|
certificates.append(certificate)
|
|
213
238
|
|
|
214
239
|
if len(resource) > 0:
|
|
215
240
|
for r in resource:
|
|
216
|
-
r[
|
|
241
|
+
r["apiId"] = api_id
|
|
217
242
|
resources.extend(resource)
|
|
218
243
|
|
|
219
244
|
return stages, certificates, resources
|
|
@@ -221,13 +246,17 @@ def transform_rest_api_details(
|
|
|
221
246
|
|
|
222
247
|
@timeit
|
|
223
248
|
def load_rest_api_details(
|
|
224
|
-
neo4j_session: neo4j.Session,
|
|
225
|
-
|
|
249
|
+
neo4j_session: neo4j.Session,
|
|
250
|
+
stages_certificate_resources: List[Tuple[Any, Any, Any, Any, Any]],
|
|
251
|
+
aws_account_id: str,
|
|
252
|
+
update_tag: int,
|
|
226
253
|
) -> None:
|
|
227
254
|
"""
|
|
228
255
|
Transform and load Stage, Client Certificate, and Resource data
|
|
229
256
|
"""
|
|
230
|
-
stages, certificates, resources = transform_rest_api_details(
|
|
257
|
+
stages, certificates, resources = transform_rest_api_details(
|
|
258
|
+
stages_certificate_resources,
|
|
259
|
+
)
|
|
231
260
|
|
|
232
261
|
load(
|
|
233
262
|
neo4j_session,
|
|
@@ -274,7 +303,7 @@ def parse_policy(api_id: str, policy: Policy) -> Optional[Dict[Any, Any]]:
|
|
|
274
303
|
else:
|
|
275
304
|
return None
|
|
276
305
|
except json.JSONDecodeError:
|
|
277
|
-
logger.
|
|
306
|
+
logger.warning(f"failed to decode policy json : {policy}")
|
|
278
307
|
return None
|
|
279
308
|
else:
|
|
280
309
|
return None
|
|
@@ -289,29 +318,48 @@ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
|
289
318
|
logger.info("Running API Gateway cleanup job.")
|
|
290
319
|
|
|
291
320
|
# Clean up certificates first
|
|
292
|
-
cleanup_job = GraphJob.from_node_schema(
|
|
321
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
322
|
+
APIGatewayClientCertificateSchema(),
|
|
323
|
+
common_job_parameters,
|
|
324
|
+
)
|
|
293
325
|
cleanup_job.run(neo4j_session)
|
|
294
326
|
|
|
295
327
|
# Then stages
|
|
296
|
-
cleanup_job = GraphJob.from_node_schema(
|
|
328
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
329
|
+
APIGatewayStageSchema(),
|
|
330
|
+
common_job_parameters,
|
|
331
|
+
)
|
|
297
332
|
cleanup_job.run(neo4j_session)
|
|
298
333
|
|
|
299
334
|
# Then resources
|
|
300
|
-
cleanup_job = GraphJob.from_node_schema(
|
|
335
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
336
|
+
APIGatewayResourceSchema(),
|
|
337
|
+
common_job_parameters,
|
|
338
|
+
)
|
|
301
339
|
cleanup_job.run(neo4j_session)
|
|
302
340
|
|
|
303
341
|
# Finally REST APIs
|
|
304
|
-
cleanup_job = GraphJob.from_node_schema(
|
|
342
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
343
|
+
APIGatewayRestAPISchema(),
|
|
344
|
+
common_job_parameters,
|
|
345
|
+
)
|
|
305
346
|
cleanup_job.run(neo4j_session)
|
|
306
347
|
|
|
307
348
|
|
|
308
349
|
@timeit
|
|
309
350
|
def sync_apigateway_rest_apis(
|
|
310
|
-
neo4j_session: neo4j.Session,
|
|
351
|
+
neo4j_session: neo4j.Session,
|
|
352
|
+
boto3_session: boto3.session.Session,
|
|
353
|
+
region: str,
|
|
354
|
+
current_aws_account_id: str,
|
|
311
355
|
aws_update_tag: int,
|
|
312
356
|
) -> None:
|
|
313
357
|
rest_apis = get_apigateway_rest_apis(boto3_session, region)
|
|
314
|
-
stages_certificate_resources = get_rest_api_details(
|
|
358
|
+
stages_certificate_resources = get_rest_api_details(
|
|
359
|
+
boto3_session,
|
|
360
|
+
rest_apis,
|
|
361
|
+
region,
|
|
362
|
+
)
|
|
315
363
|
|
|
316
364
|
# Extract policies and transform the data
|
|
317
365
|
policies = []
|
|
@@ -327,16 +375,39 @@ def sync_apigateway_rest_apis(
|
|
|
327
375
|
current_aws_account_id,
|
|
328
376
|
aws_update_tag,
|
|
329
377
|
)
|
|
330
|
-
load_apigateway_rest_apis(
|
|
331
|
-
|
|
378
|
+
load_apigateway_rest_apis(
|
|
379
|
+
neo4j_session,
|
|
380
|
+
transformed_apis,
|
|
381
|
+
region,
|
|
382
|
+
current_aws_account_id,
|
|
383
|
+
aws_update_tag,
|
|
384
|
+
)
|
|
385
|
+
load_rest_api_details(
|
|
386
|
+
neo4j_session,
|
|
387
|
+
stages_certificate_resources,
|
|
388
|
+
current_aws_account_id,
|
|
389
|
+
aws_update_tag,
|
|
390
|
+
)
|
|
332
391
|
|
|
333
392
|
|
|
334
393
|
@timeit
|
|
335
394
|
def sync(
|
|
336
|
-
neo4j_session: neo4j.Session,
|
|
337
|
-
|
|
395
|
+
neo4j_session: neo4j.Session,
|
|
396
|
+
boto3_session: boto3.session.Session,
|
|
397
|
+
regions: List[str],
|
|
398
|
+
current_aws_account_id: str,
|
|
399
|
+
update_tag: int,
|
|
400
|
+
common_job_parameters: Dict,
|
|
338
401
|
) -> None:
|
|
339
402
|
for region in regions:
|
|
340
|
-
logger.info(
|
|
341
|
-
|
|
403
|
+
logger.info(
|
|
404
|
+
f"Syncing AWS APIGateway Rest APIs for region '{region}' in account '{current_aws_account_id}'.",
|
|
405
|
+
)
|
|
406
|
+
sync_apigateway_rest_apis(
|
|
407
|
+
neo4j_session,
|
|
408
|
+
boto3_session,
|
|
409
|
+
region,
|
|
410
|
+
current_aws_account_id,
|
|
411
|
+
update_tag,
|
|
412
|
+
)
|
|
342
413
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import botocore.exceptions
|
|
8
|
+
import neo4j
|
|
9
|
+
|
|
10
|
+
from cartography.client.core.tx import load
|
|
11
|
+
from cartography.graph.job import GraphJob
|
|
12
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
13
|
+
from cartography.models.aws.cloudtrail.trail import CloudTrailTrailSchema
|
|
14
|
+
from cartography.util import aws_handle_regions
|
|
15
|
+
from cartography.util import timeit
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@timeit
|
|
21
|
+
@aws_handle_regions
|
|
22
|
+
def get_cloudtrail_trails(
|
|
23
|
+
boto3_session: boto3.Session, region: str
|
|
24
|
+
) -> List[Dict[str, Any]]:
|
|
25
|
+
client = boto3_session.client(
|
|
26
|
+
"cloudtrail", region_name=region, config=get_botocore_config()
|
|
27
|
+
)
|
|
28
|
+
paginator = client.get_paginator("list_trails")
|
|
29
|
+
trails = []
|
|
30
|
+
for page in paginator.paginate():
|
|
31
|
+
trails.extend(page["Trails"])
|
|
32
|
+
|
|
33
|
+
# CloudTrail multi-region trails are shown in list_trails,
|
|
34
|
+
# but the get_trail call only works in the home region
|
|
35
|
+
trails_filtered = [trail for trail in trails if trail.get("HomeRegion") == region]
|
|
36
|
+
return trails_filtered
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@timeit
|
|
40
|
+
def get_cloudtrail_trail(
|
|
41
|
+
boto3_session: boto3.Session,
|
|
42
|
+
region: str,
|
|
43
|
+
trail_name: str,
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
client = boto3_session.client(
|
|
46
|
+
"cloudtrail", region_name=region, config=get_botocore_config()
|
|
47
|
+
)
|
|
48
|
+
trail_details: Dict[str, Any] = {}
|
|
49
|
+
try:
|
|
50
|
+
response = client.get_trail(Name=trail_name)
|
|
51
|
+
trail_details = response["Trail"]
|
|
52
|
+
except botocore.exceptions.ClientError as e:
|
|
53
|
+
code = e.response["Error"]["Code"]
|
|
54
|
+
msg = e.response["Error"]["Message"]
|
|
55
|
+
logger.warning(
|
|
56
|
+
f"Could not run CloudTrail get_trail due to boto3 error {code}: {msg}. Skipping.",
|
|
57
|
+
)
|
|
58
|
+
return trail_details
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@timeit
|
|
62
|
+
def load_cloudtrail_trails(
|
|
63
|
+
neo4j_session: neo4j.Session,
|
|
64
|
+
data: List[Dict[str, Any]],
|
|
65
|
+
region: str,
|
|
66
|
+
current_aws_account_id: str,
|
|
67
|
+
aws_update_tag: int,
|
|
68
|
+
) -> None:
|
|
69
|
+
logger.info(
|
|
70
|
+
f"Loading CloudTrail {len(data)} trails for region '{region}' into graph.",
|
|
71
|
+
)
|
|
72
|
+
load(
|
|
73
|
+
neo4j_session,
|
|
74
|
+
CloudTrailTrailSchema(),
|
|
75
|
+
data,
|
|
76
|
+
lastupdated=aws_update_tag,
|
|
77
|
+
Region=region,
|
|
78
|
+
AWS_ID=current_aws_account_id,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@timeit
|
|
83
|
+
def cleanup(
|
|
84
|
+
neo4j_session: neo4j.Session,
|
|
85
|
+
common_job_parameters: Dict[str, Any],
|
|
86
|
+
) -> None:
|
|
87
|
+
logger.debug("Running CloudTrail cleanup job.")
|
|
88
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
89
|
+
CloudTrailTrailSchema(), common_job_parameters
|
|
90
|
+
)
|
|
91
|
+
cleanup_job.run(neo4j_session)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@timeit
|
|
95
|
+
def sync(
|
|
96
|
+
neo4j_session: neo4j.Session,
|
|
97
|
+
boto3_session: boto3.session.Session,
|
|
98
|
+
regions: List[str],
|
|
99
|
+
current_aws_account_id: str,
|
|
100
|
+
update_tag: int,
|
|
101
|
+
common_job_parameters: Dict[str, Any],
|
|
102
|
+
) -> None:
|
|
103
|
+
for region in regions:
|
|
104
|
+
logger.info(
|
|
105
|
+
f"Syncing CloudTrail for region '{region}' in account '{current_aws_account_id}'.",
|
|
106
|
+
)
|
|
107
|
+
trails = get_cloudtrail_trails(boto3_session, region)
|
|
108
|
+
trail_data: List[Dict[str, Any]] = []
|
|
109
|
+
for trail in trails:
|
|
110
|
+
trail_name = trail["Name"]
|
|
111
|
+
trail_details = get_cloudtrail_trail(
|
|
112
|
+
boto3_session,
|
|
113
|
+
region,
|
|
114
|
+
trail_name,
|
|
115
|
+
)
|
|
116
|
+
if trail_details:
|
|
117
|
+
trail_data.append(trail_details)
|
|
118
|
+
|
|
119
|
+
load_cloudtrail_trails(
|
|
120
|
+
neo4j_session,
|
|
121
|
+
trail_data,
|
|
122
|
+
region,
|
|
123
|
+
current_aws_account_id,
|
|
124
|
+
update_tag,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
|
+
from cartography.models.aws.cloudwatch.loggroup import CloudWatchLogGroupSchema
|
|
13
|
+
from cartography.util import aws_handle_regions
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
@aws_handle_regions
|
|
21
|
+
def get_cloudwatch_log_groups(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"cloudwatch", region_name=region, config=get_botocore_config()
|
|
26
|
+
)
|
|
27
|
+
paginator = client.get_paginator("describe_log_groups")
|
|
28
|
+
logGroups = []
|
|
29
|
+
for page in paginator.paginate():
|
|
30
|
+
logGroups.extend(page["logGroups"])
|
|
31
|
+
return logGroups
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@timeit
|
|
35
|
+
def load_cloudwatch_log_groups(
|
|
36
|
+
neo4j_session: neo4j.Session,
|
|
37
|
+
data: List[Dict[str, Any]],
|
|
38
|
+
region: str,
|
|
39
|
+
current_aws_account_id: str,
|
|
40
|
+
aws_update_tag: int,
|
|
41
|
+
) -> None:
|
|
42
|
+
logger.info(
|
|
43
|
+
f"Loading CloudWatch {len(data)} log groups for region '{region}' into graph.",
|
|
44
|
+
)
|
|
45
|
+
load(
|
|
46
|
+
neo4j_session,
|
|
47
|
+
CloudWatchLogGroupSchema(),
|
|
48
|
+
data,
|
|
49
|
+
lastupdated=aws_update_tag,
|
|
50
|
+
Region=region,
|
|
51
|
+
AWS_ID=current_aws_account_id,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@timeit
|
|
56
|
+
def cleanup(
|
|
57
|
+
neo4j_session: neo4j.Session,
|
|
58
|
+
common_job_parameters: Dict[str, Any],
|
|
59
|
+
) -> None:
|
|
60
|
+
logger.debug("Running CloudWatch cleanup job.")
|
|
61
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
62
|
+
CloudWatchLogGroupSchema(), common_job_parameters
|
|
63
|
+
)
|
|
64
|
+
cleanup_job.run(neo4j_session)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@timeit
|
|
68
|
+
def sync(
|
|
69
|
+
neo4j_session: neo4j.Session,
|
|
70
|
+
boto3_session: boto3.session.Session,
|
|
71
|
+
regions: List[str],
|
|
72
|
+
current_aws_account_id: str,
|
|
73
|
+
update_tag: int,
|
|
74
|
+
common_job_parameters: Dict[str, Any],
|
|
75
|
+
) -> None:
|
|
76
|
+
for region in regions:
|
|
77
|
+
logger.info(
|
|
78
|
+
f"Syncing CloudWatch for region '{region}' in account '{current_aws_account_id}'.",
|
|
79
|
+
)
|
|
80
|
+
logGroups = get_cloudwatch_log_groups(boto3_session, region)
|
|
81
|
+
group_data: List[Dict[str, Any]] = []
|
|
82
|
+
for logGroup in logGroups:
|
|
83
|
+
group_data.append(logGroup)
|
|
84
|
+
|
|
85
|
+
load_cloudwatch_log_groups(
|
|
86
|
+
neo4j_session,
|
|
87
|
+
group_data,
|
|
88
|
+
region,
|
|
89
|
+
current_aws_account_id,
|
|
90
|
+
update_tag,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
cleanup(neo4j_session, common_job_parameters)
|