cartography 0.102.0rc2__py3-none-any.whl → 0.103.0rc1__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 +302 -253
- cartography/client/core/tx.py +39 -18
- cartography/config.py +4 -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/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/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 +53 -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/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/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/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/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/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/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/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/stats.py +3 -3
- cartography/sync.py +107 -31
- cartography/util.py +84 -62
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/METADATA +3 -14
- cartography-0.103.0rc1.dist-info/RECORD +396 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc2.dist-info/RECORD +0 -381
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -6,30 +6,43 @@ import boto3
|
|
|
6
6
|
import neo4j
|
|
7
7
|
from botocore.exceptions import ClientError
|
|
8
8
|
|
|
9
|
-
from .util import get_botocore_config
|
|
10
9
|
from cartography.util import aws_handle_regions
|
|
11
10
|
from cartography.util import run_cleanup_job
|
|
12
11
|
from cartography.util import timeit
|
|
13
12
|
|
|
13
|
+
from .util import get_botocore_config
|
|
14
|
+
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@timeit
|
|
18
19
|
@aws_handle_regions
|
|
19
|
-
def get_reserved_instances(
|
|
20
|
-
|
|
20
|
+
def get_reserved_instances(
|
|
21
|
+
boto3_session: boto3.session.Session,
|
|
22
|
+
region: str,
|
|
23
|
+
) -> List[Dict]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"ec2",
|
|
26
|
+
region_name=region,
|
|
27
|
+
config=get_botocore_config(),
|
|
28
|
+
)
|
|
21
29
|
try:
|
|
22
|
-
reserved_instances = client.describe_reserved_instances()[
|
|
30
|
+
reserved_instances = client.describe_reserved_instances()["ReservedInstances"]
|
|
23
31
|
except ClientError as e:
|
|
24
|
-
logger.warning(
|
|
32
|
+
logger.warning(
|
|
33
|
+
f"Failed retrieve reserved instances for region - {region}. Error - {e}",
|
|
34
|
+
)
|
|
25
35
|
raise
|
|
26
36
|
return reserved_instances
|
|
27
37
|
|
|
28
38
|
|
|
29
39
|
@timeit
|
|
30
40
|
def load_reserved_instances(
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
neo4j_session: neo4j.Session,
|
|
42
|
+
data: List[Dict],
|
|
43
|
+
region: str,
|
|
44
|
+
current_aws_account_id: str,
|
|
45
|
+
update_tag: int,
|
|
33
46
|
) -> None:
|
|
34
47
|
ingest_reserved_instances = """
|
|
35
48
|
UNWIND $reserved_instances_list as res
|
|
@@ -48,8 +61,8 @@ def load_reserved_instances(
|
|
|
48
61
|
"""
|
|
49
62
|
|
|
50
63
|
for r_instance in data:
|
|
51
|
-
r_instance[
|
|
52
|
-
r_instance[
|
|
64
|
+
r_instance["Start"] = str(r_instance["Start"])
|
|
65
|
+
r_instance["End"] = str(r_instance["End"])
|
|
53
66
|
|
|
54
67
|
neo4j_session.run(
|
|
55
68
|
ingest_reserved_instances,
|
|
@@ -61,9 +74,12 @@ def load_reserved_instances(
|
|
|
61
74
|
|
|
62
75
|
|
|
63
76
|
@timeit
|
|
64
|
-
def cleanup_reserved_instances(
|
|
77
|
+
def cleanup_reserved_instances(
|
|
78
|
+
neo4j_session: neo4j.Session,
|
|
79
|
+
common_job_parameters: Dict,
|
|
80
|
+
) -> None:
|
|
65
81
|
run_cleanup_job(
|
|
66
|
-
|
|
82
|
+
"aws_import_reserved_instances_cleanup.json",
|
|
67
83
|
neo4j_session,
|
|
68
84
|
common_job_parameters,
|
|
69
85
|
)
|
|
@@ -71,12 +87,25 @@ def cleanup_reserved_instances(neo4j_session: neo4j.Session, common_job_paramete
|
|
|
71
87
|
|
|
72
88
|
@timeit
|
|
73
89
|
def sync_ec2_reserved_instances(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
neo4j_session: neo4j.Session,
|
|
91
|
+
boto3_session: boto3.session.Session,
|
|
92
|
+
regions: List[str],
|
|
93
|
+
current_aws_account_id: str,
|
|
94
|
+
update_tag: int,
|
|
95
|
+
common_job_parameters: Dict,
|
|
77
96
|
) -> None:
|
|
78
97
|
for region in regions:
|
|
79
|
-
logger.debug(
|
|
98
|
+
logger.debug(
|
|
99
|
+
"Syncing reserved instances for region '%s' in account '%s'.",
|
|
100
|
+
region,
|
|
101
|
+
current_aws_account_id,
|
|
102
|
+
)
|
|
80
103
|
data = get_reserved_instances(boto3_session, region)
|
|
81
|
-
load_reserved_instances(
|
|
104
|
+
load_reserved_instances(
|
|
105
|
+
neo4j_session,
|
|
106
|
+
data,
|
|
107
|
+
region,
|
|
108
|
+
current_aws_account_id,
|
|
109
|
+
update_tag,
|
|
110
|
+
)
|
|
82
111
|
cleanup_reserved_instances(neo4j_session, common_job_parameters)
|
|
@@ -7,7 +7,9 @@ import neo4j
|
|
|
7
7
|
from cartography.client.core.tx import load
|
|
8
8
|
from cartography.graph.job import GraphJob
|
|
9
9
|
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
10
|
-
from cartography.models.aws.ec2.route_table_associations import
|
|
10
|
+
from cartography.models.aws.ec2.route_table_associations import (
|
|
11
|
+
RouteTableAssociationSchema,
|
|
12
|
+
)
|
|
11
13
|
from cartography.models.aws.ec2.route_tables import RouteTableSchema
|
|
12
14
|
from cartography.models.aws.ec2.routes import RouteSchema
|
|
13
15
|
from cartography.util import aws_handle_regions
|
|
@@ -16,7 +18,9 @@ from cartography.util import timeit
|
|
|
16
18
|
logger = logging.getLogger(__name__)
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
def _get_route_id_and_target(
|
|
21
|
+
def _get_route_id_and_target(
|
|
22
|
+
route_table_id: str, route: dict[str, Any]
|
|
23
|
+
) -> tuple[str, str | None]:
|
|
20
24
|
"""
|
|
21
25
|
Generate a unique identifier for an AWS EC2 route and return the target of the route
|
|
22
26
|
regardless of its type.
|
|
@@ -29,18 +33,18 @@ def _get_route_id_and_target(route_table_id: str, route: dict[str, Any]) -> tupl
|
|
|
29
33
|
A tuple containing the unique identifier for the route and the target of the route
|
|
30
34
|
"""
|
|
31
35
|
route_target_keys = [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
"DestinationCidrBlock",
|
|
37
|
+
"DestinationIpv6CidrBlock",
|
|
38
|
+
"GatewayId",
|
|
39
|
+
"InstanceId",
|
|
40
|
+
"NatGatewayId",
|
|
41
|
+
"TransitGatewayId",
|
|
42
|
+
"LocalGatewayId",
|
|
43
|
+
"CarrierGatewayId",
|
|
44
|
+
"NetworkInterfaceId",
|
|
45
|
+
"VpcPeeringConnectionId",
|
|
46
|
+
"EgressOnlyInternetGatewayId",
|
|
47
|
+
"CoreNetworkArn",
|
|
44
48
|
]
|
|
45
49
|
|
|
46
50
|
# Start with the route table ID
|
|
@@ -63,23 +67,27 @@ def _get_route_id_and_target(route_table_id: str, route: dict[str, Any]) -> tupl
|
|
|
63
67
|
"so that we can update the available keys.",
|
|
64
68
|
)
|
|
65
69
|
|
|
66
|
-
return
|
|
70
|
+
return "|".join(parts), target
|
|
67
71
|
|
|
68
72
|
|
|
69
73
|
@timeit
|
|
70
74
|
@aws_handle_regions
|
|
71
|
-
def get_route_tables(
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
def get_route_tables(
|
|
76
|
+
boto3_session: boto3.session.Session, region: str
|
|
77
|
+
) -> list[dict[str, Any]]:
|
|
78
|
+
client = boto3_session.client(
|
|
79
|
+
"ec2", region_name=region, config=get_botocore_config()
|
|
80
|
+
)
|
|
81
|
+
paginator = client.get_paginator("describe_route_tables")
|
|
74
82
|
route_tables: list[dict[str, Any]] = []
|
|
75
83
|
for page in paginator.paginate():
|
|
76
|
-
route_tables.extend(page[
|
|
84
|
+
route_tables.extend(page["RouteTables"])
|
|
77
85
|
return route_tables
|
|
78
86
|
|
|
79
87
|
|
|
80
88
|
def _transform_route_table_associations(
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
route_table_id: str,
|
|
90
|
+
associations: list[dict[str, Any]],
|
|
83
91
|
) -> tuple[list[dict[str, Any]], bool]:
|
|
84
92
|
"""
|
|
85
93
|
Transform route table association data into a format suitable for cartography ingestion.
|
|
@@ -96,29 +104,33 @@ def _transform_route_table_associations(
|
|
|
96
104
|
transformed = []
|
|
97
105
|
is_main = False
|
|
98
106
|
for association in associations:
|
|
99
|
-
if association.get(
|
|
100
|
-
target = association[
|
|
101
|
-
elif association.get(
|
|
102
|
-
target = association[
|
|
107
|
+
if association.get("SubnetId"):
|
|
108
|
+
target = association["SubnetId"]
|
|
109
|
+
elif association.get("GatewayId"):
|
|
110
|
+
target = association["GatewayId"]
|
|
103
111
|
else:
|
|
104
112
|
is_main = True
|
|
105
|
-
target =
|
|
113
|
+
target = "main"
|
|
106
114
|
|
|
107
115
|
transformed_association = {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
"id": association["RouteTableAssociationId"],
|
|
117
|
+
"route_table_id": route_table_id,
|
|
118
|
+
"subnet_id": association.get("SubnetId"),
|
|
119
|
+
"gateway_id": association.get("GatewayId"),
|
|
120
|
+
"main": association.get("Main", False),
|
|
121
|
+
"association_state": association.get("AssociationState", {}).get("State"),
|
|
122
|
+
"association_state_message": association.get("AssociationState", {}).get(
|
|
123
|
+
"Message"
|
|
124
|
+
),
|
|
125
|
+
"_target": target,
|
|
116
126
|
}
|
|
117
127
|
transformed.append(transformed_association)
|
|
118
128
|
return transformed, is_main
|
|
119
129
|
|
|
120
130
|
|
|
121
|
-
def _transform_route_table_routes(
|
|
131
|
+
def _transform_route_table_routes(
|
|
132
|
+
route_table_id: str, routes: list[dict[str, Any]]
|
|
133
|
+
) -> list[dict[str, Any]]:
|
|
122
134
|
"""
|
|
123
135
|
Transform route table route data into a format suitable for cartography ingestion.
|
|
124
136
|
|
|
@@ -134,32 +146,32 @@ def _transform_route_table_routes(route_table_id: str, routes: list[dict[str, An
|
|
|
134
146
|
route_id, target = _get_route_id_and_target(route_table_id, route)
|
|
135
147
|
|
|
136
148
|
transformed_route = {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
"id": route_id,
|
|
150
|
+
"route_table_id": route_table_id,
|
|
151
|
+
"destination_cidr_block": route.get("DestinationCidrBlock"),
|
|
152
|
+
"destination_ipv6_cidr_block": route.get("DestinationIpv6CidrBlock"),
|
|
153
|
+
"gateway_id": route.get("GatewayId"),
|
|
154
|
+
"instance_id": route.get("InstanceId"),
|
|
155
|
+
"instance_owner_id": route.get("InstanceOwnerId"),
|
|
156
|
+
"nat_gateway_id": route.get("NatGatewayId"),
|
|
157
|
+
"transit_gateway_id": route.get("TransitGatewayId"),
|
|
158
|
+
"local_gateway_id": route.get("LocalGatewayId"),
|
|
159
|
+
"carrier_gateway_id": route.get("CarrierGatewayId"),
|
|
160
|
+
"network_interface_id": route.get("NetworkInterfaceId"),
|
|
161
|
+
"vpc_peering_connection_id": route.get("VpcPeeringConnectionId"),
|
|
162
|
+
"state": route.get("State"),
|
|
163
|
+
"origin": route.get("Origin"),
|
|
164
|
+
"core_network_arn": route.get("CoreNetworkArn"),
|
|
165
|
+
"destination_prefix_list_id": route.get("DestinationPrefixListId"),
|
|
166
|
+
"egress_only_internet_gateway_id": route.get("EgressOnlyInternetGatewayId"),
|
|
167
|
+
"_target": target,
|
|
156
168
|
}
|
|
157
169
|
transformed.append(transformed_route)
|
|
158
170
|
return transformed
|
|
159
171
|
|
|
160
172
|
|
|
161
173
|
def transform_route_table_data(
|
|
162
|
-
|
|
174
|
+
route_tables: list[dict[str, Any]],
|
|
163
175
|
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
|
|
164
176
|
"""
|
|
165
177
|
Transform route table data into a format suitable for cartography ingestion.
|
|
@@ -175,31 +187,37 @@ def transform_route_table_data(
|
|
|
175
187
|
route_data = []
|
|
176
188
|
|
|
177
189
|
for rt in route_tables:
|
|
178
|
-
route_table_id = rt[
|
|
190
|
+
route_table_id = rt["RouteTableId"]
|
|
179
191
|
|
|
180
192
|
# Transform routes
|
|
181
193
|
current_routes = []
|
|
182
|
-
if rt.get(
|
|
183
|
-
current_routes = _transform_route_table_routes(route_table_id, rt[
|
|
194
|
+
if rt.get("Routes"):
|
|
195
|
+
current_routes = _transform_route_table_routes(route_table_id, rt["Routes"])
|
|
184
196
|
route_data.extend(current_routes)
|
|
185
197
|
|
|
186
198
|
# If the rt has a association marked with main=True, then it is the main route table for the VPC.
|
|
187
199
|
is_main = False
|
|
188
200
|
# Transform associations
|
|
189
|
-
if rt.get(
|
|
190
|
-
associations, is_main = _transform_route_table_associations(
|
|
201
|
+
if rt.get("Associations"):
|
|
202
|
+
associations, is_main = _transform_route_table_associations(
|
|
203
|
+
route_table_id, rt["Associations"]
|
|
204
|
+
)
|
|
191
205
|
association_data.extend(associations)
|
|
192
206
|
|
|
193
207
|
transformed_rt = {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
"id": route_table_id,
|
|
209
|
+
"route_table_id": route_table_id,
|
|
210
|
+
"owner_id": rt.get("OwnerId"),
|
|
211
|
+
"vpc_id": rt.get("VpcId"),
|
|
212
|
+
"VpnGatewayIds": [
|
|
213
|
+
vgw["GatewayId"] for vgw in rt.get("PropagatingVgws", [])
|
|
214
|
+
],
|
|
215
|
+
"RouteTableAssociationIds": [
|
|
216
|
+
assoc["RouteTableAssociationId"] for assoc in rt.get("Associations", [])
|
|
217
|
+
],
|
|
218
|
+
"RouteIds": [route["id"] for route in current_routes],
|
|
219
|
+
"tags": rt.get("Tags", []),
|
|
220
|
+
"main": is_main,
|
|
203
221
|
}
|
|
204
222
|
transformed_tables.append(transformed_rt)
|
|
205
223
|
|
|
@@ -208,11 +226,11 @@ def transform_route_table_data(
|
|
|
208
226
|
|
|
209
227
|
@timeit
|
|
210
228
|
def load_route_tables(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
229
|
+
neo4j_session: neo4j.Session,
|
|
230
|
+
data: list[dict[str, Any]],
|
|
231
|
+
region: str,
|
|
232
|
+
current_aws_account_id: str,
|
|
233
|
+
update_tag: int,
|
|
216
234
|
) -> None:
|
|
217
235
|
load(
|
|
218
236
|
neo4j_session,
|
|
@@ -226,11 +244,11 @@ def load_route_tables(
|
|
|
226
244
|
|
|
227
245
|
@timeit
|
|
228
246
|
def load_route_table_associations(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
247
|
+
neo4j_session: neo4j.Session,
|
|
248
|
+
data: list[dict[str, Any]],
|
|
249
|
+
region: str,
|
|
250
|
+
current_aws_account_id: str,
|
|
251
|
+
update_tag: int,
|
|
234
252
|
) -> None:
|
|
235
253
|
load(
|
|
236
254
|
neo4j_session,
|
|
@@ -244,11 +262,11 @@ def load_route_table_associations(
|
|
|
244
262
|
|
|
245
263
|
@timeit
|
|
246
264
|
def load_routes(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
265
|
+
neo4j_session: neo4j.Session,
|
|
266
|
+
data: list[dict[str, Any]],
|
|
267
|
+
region: str,
|
|
268
|
+
current_aws_account_id: str,
|
|
269
|
+
update_tag: int,
|
|
252
270
|
) -> None:
|
|
253
271
|
load(
|
|
254
272
|
neo4j_session,
|
|
@@ -261,27 +279,49 @@ def load_routes(
|
|
|
261
279
|
|
|
262
280
|
|
|
263
281
|
@timeit
|
|
264
|
-
def cleanup(
|
|
282
|
+
def cleanup(
|
|
283
|
+
neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
|
|
284
|
+
) -> None:
|
|
265
285
|
logger.debug("Running EC2 route tables cleanup")
|
|
266
|
-
GraphJob.from_node_schema(RouteTableSchema(), common_job_parameters).run(
|
|
286
|
+
GraphJob.from_node_schema(RouteTableSchema(), common_job_parameters).run(
|
|
287
|
+
neo4j_session
|
|
288
|
+
)
|
|
267
289
|
GraphJob.from_node_schema(RouteSchema(), common_job_parameters).run(neo4j_session)
|
|
268
|
-
GraphJob.from_node_schema(RouteTableAssociationSchema(), common_job_parameters).run(
|
|
290
|
+
GraphJob.from_node_schema(RouteTableAssociationSchema(), common_job_parameters).run(
|
|
291
|
+
neo4j_session
|
|
292
|
+
)
|
|
269
293
|
|
|
270
294
|
|
|
271
295
|
@timeit
|
|
272
296
|
def sync_route_tables(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
297
|
+
neo4j_session: neo4j.Session,
|
|
298
|
+
boto3_session: boto3.session.Session,
|
|
299
|
+
regions: list[str],
|
|
300
|
+
current_aws_account_id: str,
|
|
301
|
+
update_tag: int,
|
|
302
|
+
common_job_parameters: dict[str, Any],
|
|
279
303
|
) -> None:
|
|
280
304
|
for region in regions:
|
|
281
|
-
logger.info(
|
|
305
|
+
logger.info(
|
|
306
|
+
"Syncing EC2 route tables for region '%s' in account '%s'.",
|
|
307
|
+
region,
|
|
308
|
+
current_aws_account_id,
|
|
309
|
+
)
|
|
282
310
|
route_tables = get_route_tables(boto3_session, region)
|
|
283
|
-
transformed_tables, association_data, route_data = transform_route_table_data(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
311
|
+
transformed_tables, association_data, route_data = transform_route_table_data(
|
|
312
|
+
route_tables
|
|
313
|
+
)
|
|
314
|
+
load_routes(
|
|
315
|
+
neo4j_session, route_data, region, current_aws_account_id, update_tag
|
|
316
|
+
)
|
|
317
|
+
load_route_table_associations(
|
|
318
|
+
neo4j_session, association_data, region, current_aws_account_id, update_tag
|
|
319
|
+
)
|
|
320
|
+
load_route_tables(
|
|
321
|
+
neo4j_session,
|
|
322
|
+
transformed_tables,
|
|
323
|
+
region,
|
|
324
|
+
current_aws_account_id,
|
|
325
|
+
update_tag,
|
|
326
|
+
)
|
|
287
327
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -6,30 +6,46 @@ from typing import List
|
|
|
6
6
|
import boto3
|
|
7
7
|
import neo4j
|
|
8
8
|
|
|
9
|
-
from .util import get_botocore_config
|
|
10
9
|
from cartography.graph.job import GraphJob
|
|
11
|
-
from cartography.models.aws.ec2.securitygroup_instance import
|
|
10
|
+
from cartography.models.aws.ec2.securitygroup_instance import (
|
|
11
|
+
EC2SecurityGroupInstanceSchema,
|
|
12
|
+
)
|
|
12
13
|
from cartography.util import aws_handle_regions
|
|
13
14
|
from cartography.util import run_cleanup_job
|
|
14
15
|
from cartography.util import timeit
|
|
15
16
|
|
|
17
|
+
from .util import get_botocore_config
|
|
18
|
+
|
|
16
19
|
logger = logging.getLogger(__name__)
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
@timeit
|
|
20
23
|
@aws_handle_regions
|
|
21
|
-
def get_ec2_security_group_data(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
def get_ec2_security_group_data(
|
|
25
|
+
boto3_session: boto3.session.Session,
|
|
26
|
+
region: str,
|
|
27
|
+
) -> List[Dict]:
|
|
28
|
+
client = boto3_session.client(
|
|
29
|
+
"ec2",
|
|
30
|
+
region_name=region,
|
|
31
|
+
config=get_botocore_config(),
|
|
32
|
+
)
|
|
33
|
+
paginator = client.get_paginator("describe_security_groups")
|
|
24
34
|
security_groups: List[Dict] = []
|
|
25
35
|
for page in paginator.paginate():
|
|
26
|
-
security_groups.extend(page[
|
|
36
|
+
security_groups.extend(page["SecurityGroups"])
|
|
27
37
|
return security_groups
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
@timeit
|
|
31
|
-
def load_ec2_security_group_rule(
|
|
32
|
-
|
|
41
|
+
def load_ec2_security_group_rule(
|
|
42
|
+
neo4j_session: neo4j.Session,
|
|
43
|
+
group: Dict,
|
|
44
|
+
rule_type: str,
|
|
45
|
+
update_tag: int,
|
|
46
|
+
) -> None:
|
|
47
|
+
INGEST_RULE_TEMPLATE = Template(
|
|
48
|
+
"""
|
|
33
49
|
MERGE (rule:$rule_label{ruleid: $RuleId})
|
|
34
50
|
ON CREATE SET rule :IpRule, rule.firstseen = timestamp(), rule.fromport = $FromPort, rule.toport = $ToPort,
|
|
35
51
|
rule.protocol = $Protocol
|
|
@@ -39,7 +55,8 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
|
|
|
39
55
|
MERGE (group)<-[r:MEMBER_OF_EC2_SECURITY_GROUP]-(rule)
|
|
40
56
|
ON CREATE SET r.firstseen = timestamp()
|
|
41
57
|
SET r.lastupdated = $update_tag;
|
|
42
|
-
"""
|
|
58
|
+
""",
|
|
59
|
+
)
|
|
43
60
|
|
|
44
61
|
ingest_rule_group_pair = """
|
|
45
62
|
MERGE (group:EC2SecurityGroup{id: $GroupId})
|
|
@@ -64,7 +81,10 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
|
|
|
64
81
|
"""
|
|
65
82
|
|
|
66
83
|
group_id = group["GroupId"]
|
|
67
|
-
rule_type_map = {
|
|
84
|
+
rule_type_map = {
|
|
85
|
+
"IpPermissions": "IpPermissionInbound",
|
|
86
|
+
"IpPermissionsEgress": "IpPermissionEgress",
|
|
87
|
+
}
|
|
68
88
|
|
|
69
89
|
if group.get(rule_type):
|
|
70
90
|
for rule in group[rule_type]:
|
|
@@ -76,7 +96,9 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
|
|
|
76
96
|
# NOTE Cypher query syntax is incompatible with Python string formatting, so we have to do this awkward
|
|
77
97
|
# NOTE manual formatting instead.
|
|
78
98
|
neo4j_session.run(
|
|
79
|
-
INGEST_RULE_TEMPLATE.safe_substitute(
|
|
99
|
+
INGEST_RULE_TEMPLATE.safe_substitute(
|
|
100
|
+
rule_label=rule_type_map[rule_type],
|
|
101
|
+
),
|
|
80
102
|
RuleId=ruleid,
|
|
81
103
|
FromPort=from_port,
|
|
82
104
|
ToPort=to_port,
|
|
@@ -104,8 +126,11 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
|
|
|
104
126
|
|
|
105
127
|
@timeit
|
|
106
128
|
def load_ec2_security_groupinfo(
|
|
107
|
-
neo4j_session: neo4j.Session,
|
|
108
|
-
|
|
129
|
+
neo4j_session: neo4j.Session,
|
|
130
|
+
data: List[Dict],
|
|
131
|
+
region: str,
|
|
132
|
+
current_aws_account_id: str,
|
|
133
|
+
update_tag: int,
|
|
109
134
|
) -> None:
|
|
110
135
|
ingest_security_group = """
|
|
111
136
|
MERGE (group:EC2SecurityGroup{id: $GroupId})
|
|
@@ -138,26 +163,51 @@ def load_ec2_security_groupinfo(
|
|
|
138
163
|
)
|
|
139
164
|
|
|
140
165
|
load_ec2_security_group_rule(neo4j_session, group, "IpPermissions", update_tag)
|
|
141
|
-
load_ec2_security_group_rule(
|
|
166
|
+
load_ec2_security_group_rule(
|
|
167
|
+
neo4j_session,
|
|
168
|
+
group,
|
|
169
|
+
"IpPermissionsEgress",
|
|
170
|
+
update_tag,
|
|
171
|
+
)
|
|
142
172
|
|
|
143
173
|
|
|
144
174
|
@timeit
|
|
145
|
-
def cleanup_ec2_security_groupinfo(
|
|
175
|
+
def cleanup_ec2_security_groupinfo(
|
|
176
|
+
neo4j_session: neo4j.Session,
|
|
177
|
+
common_job_parameters: Dict,
|
|
178
|
+
) -> None:
|
|
146
179
|
run_cleanup_job(
|
|
147
|
-
|
|
180
|
+
"aws_import_ec2_security_groupinfo_cleanup.json",
|
|
148
181
|
neo4j_session,
|
|
149
182
|
common_job_parameters,
|
|
150
183
|
)
|
|
151
|
-
GraphJob.from_node_schema(
|
|
184
|
+
GraphJob.from_node_schema(
|
|
185
|
+
EC2SecurityGroupInstanceSchema(),
|
|
186
|
+
common_job_parameters,
|
|
187
|
+
).run(neo4j_session)
|
|
152
188
|
|
|
153
189
|
|
|
154
190
|
@timeit
|
|
155
191
|
def sync_ec2_security_groupinfo(
|
|
156
|
-
neo4j_session: neo4j.Session,
|
|
157
|
-
|
|
192
|
+
neo4j_session: neo4j.Session,
|
|
193
|
+
boto3_session: boto3.session.Session,
|
|
194
|
+
regions: List[str],
|
|
195
|
+
current_aws_account_id: str,
|
|
196
|
+
update_tag: int,
|
|
197
|
+
common_job_parameters: Dict,
|
|
158
198
|
) -> None:
|
|
159
199
|
for region in regions:
|
|
160
|
-
logger.info(
|
|
200
|
+
logger.info(
|
|
201
|
+
"Syncing EC2 security groups for region '%s' in account '%s'.",
|
|
202
|
+
region,
|
|
203
|
+
current_aws_account_id,
|
|
204
|
+
)
|
|
161
205
|
data = get_ec2_security_group_data(boto3_session, region)
|
|
162
|
-
load_ec2_security_groupinfo(
|
|
206
|
+
load_ec2_security_groupinfo(
|
|
207
|
+
neo4j_session,
|
|
208
|
+
data,
|
|
209
|
+
region,
|
|
210
|
+
current_aws_account_id,
|
|
211
|
+
update_tag,
|
|
212
|
+
)
|
|
163
213
|
cleanup_ec2_security_groupinfo(neo4j_session, common_job_parameters)
|