cartography 0.101.1rc1__py3-none-any.whl → 0.102.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.

Files changed (34) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +61 -0
  3. cartography/config.py +16 -0
  4. cartography/data/indexes.cypher +0 -6
  5. cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json +0 -5
  6. cartography/intel/aws/__init__.py +11 -1
  7. cartography/intel/aws/ec2/launch_templates.py +14 -5
  8. cartography/intel/aws/ec2/load_balancers.py +126 -148
  9. cartography/intel/aws/ec2/route_tables.py +287 -0
  10. cartography/intel/aws/resources.py +2 -0
  11. cartography/intel/aws/util/common.py +27 -0
  12. cartography/intel/crowdstrike/__init__.py +17 -5
  13. cartography/intel/crowdstrike/endpoints.py +12 -44
  14. cartography/intel/entra/__init__.py +43 -0
  15. cartography/intel/entra/users.py +205 -0
  16. cartography/intel/kandji/devices.py +27 -3
  17. cartography/models/aws/ec2/load_balancer_listeners.py +68 -0
  18. cartography/models/aws/ec2/load_balancers.py +102 -0
  19. cartography/models/aws/ec2/route_table_associations.py +87 -0
  20. cartography/models/aws/ec2/route_tables.py +121 -0
  21. cartography/models/aws/ec2/routes.py +77 -0
  22. cartography/models/crowdstrike/__init__.py +0 -0
  23. cartography/models/crowdstrike/hosts.py +49 -0
  24. cartography/models/entra/__init__.py +0 -0
  25. cartography/models/entra/tenant.py +33 -0
  26. cartography/models/entra/user.py +83 -0
  27. cartography/stats.py +1 -1
  28. cartography/sync.py +2 -0
  29. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/METADATA +4 -1
  30. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/RECORD +34 -21
  31. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/WHEEL +1 -1
  32. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/entry_points.txt +0 -0
  33. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/licenses/LICENSE +0 -0
  34. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,205 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ from azure.identity import ClientSecretCredential
6
+ from msgraph import GraphServiceClient
7
+ from msgraph.generated.models.organization import Organization
8
+ from msgraph.generated.models.user import User
9
+
10
+ from cartography.client.core.tx import load
11
+ from cartography.graph.job import GraphJob
12
+ from cartography.models.entra.tenant import EntraTenantSchema
13
+ from cartography.models.entra.user import EntraUserSchema
14
+ from cartography.util import timeit
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @timeit
20
+ async def get_tenant(client: GraphServiceClient) -> Organization:
21
+ """
22
+ Get tenant information from Microsoft Graph API
23
+ """
24
+ org = await client.organization.get()
25
+ return org.value[0] # Get the first (and typically only) tenant
26
+
27
+
28
+ @timeit
29
+ async def get_users(client: GraphServiceClient) -> list[User]:
30
+ """
31
+ Get all users from Microsoft Graph API with pagination support
32
+ """
33
+ all_users: list[User] = []
34
+ request_configuration = client.users.UsersRequestBuilderGetRequestConfiguration(
35
+ query_parameters=client.users.UsersRequestBuilderGetQueryParameters(
36
+ # Request more items per page to reduce number of API calls
37
+ top=999,
38
+ ),
39
+ )
40
+
41
+ page = await client.users.get(request_configuration=request_configuration)
42
+ while page:
43
+ all_users.extend(page.value)
44
+ if not page.odata_next_link:
45
+ break
46
+ page = await client.users.with_url(page.odata_next_link).get()
47
+
48
+ return all_users
49
+
50
+
51
+ @timeit
52
+ def transform_users(users: list[User]) -> list[dict[str, Any]]:
53
+ """
54
+ Transform the API response into the format expected by our schema
55
+ """
56
+ result: list[dict[str, Any]] = []
57
+ for user in users:
58
+ transformed_user = {
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
+ }
105
+ result.append(transformed_user)
106
+ return result
107
+
108
+
109
+ @timeit
110
+ def transform_tenant(tenant: Organization, tenant_id: str) -> dict[str, Any]:
111
+ """
112
+ Transform the tenant data into the format expected by our schema
113
+ """
114
+ return {
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
+ }
131
+
132
+
133
+ @timeit
134
+ def load_tenant(
135
+ neo4j_session: neo4j.Session,
136
+ tenant: dict[str, Any],
137
+ update_tag: int,
138
+ ) -> None:
139
+ load(
140
+ neo4j_session,
141
+ EntraTenantSchema(),
142
+ [tenant],
143
+ lastupdated=update_tag,
144
+ )
145
+
146
+
147
+ @timeit
148
+ def load_users(
149
+ neo4j_session: neo4j.Session,
150
+ users: list[dict[str, Any]],
151
+ tenant_id: str,
152
+ update_tag: int,
153
+ ) -> None:
154
+ logger.info(f"Loading {len(users)} Entra users")
155
+ load(
156
+ neo4j_session,
157
+ EntraUserSchema(),
158
+ users,
159
+ lastupdated=update_tag,
160
+ TENANT_ID=tenant_id,
161
+ )
162
+
163
+
164
+ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]) -> None:
165
+ GraphJob.from_node_schema(EntraUserSchema(), common_job_parameters).run(neo4j_session)
166
+
167
+
168
+ @timeit
169
+ async def sync_entra_users(
170
+ neo4j_session: neo4j.Session,
171
+ tenant_id: str,
172
+ client_id: str,
173
+ client_secret: str,
174
+ update_tag: int,
175
+ common_job_parameters: dict[str, Any],
176
+ ) -> None:
177
+ """
178
+ Sync Entra users and tenant information
179
+ :param neo4j_session: Neo4J session for database interface
180
+ :param tenant_id: Entra tenant ID
181
+ :param client_id: Entra application client ID
182
+ :param client_secret: Entra application client secret
183
+ :param update_tag: Timestamp used to determine data freshness
184
+ :param common_job_parameters: dict of other job parameters to carry to sub-jobs
185
+ :return: None
186
+ """
187
+ # Initialize Graph client
188
+ credential = ClientSecretCredential(
189
+ tenant_id=tenant_id,
190
+ client_id=client_id,
191
+ client_secret=client_secret,
192
+ )
193
+ client = GraphServiceClient(credential, scopes=['https://graph.microsoft.com/.default'])
194
+
195
+ # Get tenant information
196
+ tenant = await get_tenant(client)
197
+ users = await get_users(client)
198
+
199
+ transformed_users = transform_users(users)
200
+ transformed_tenant = transform_tenant(tenant, tenant_id)
201
+
202
+ load_tenant(neo4j_session, transformed_tenant, update_tag)
203
+ load_users(neo4j_session, transformed_users, tenant_id, update_tag)
204
+
205
+ cleanup(neo4j_session, common_job_parameters)
@@ -25,10 +25,34 @@ def get(kandji_base_uri: str, kandji_token: str) -> List[Dict[str, Any]]:
25
25
  'Authorization': f'Bearer {kandji_token}',
26
26
  }
27
27
 
28
+ offset = 0
29
+ limit = 300
30
+ params: dict[str, str | int] = {
31
+ "sort": "serial_number",
32
+ "limit": limit,
33
+ "offset": offset,
34
+ }
35
+
36
+ devices: List[Dict[str, Any]] = []
28
37
  session = Session()
29
- req = session.get(api_endpoint, headers=headers, timeout=_TIMEOUT)
30
- req.raise_for_status()
31
- return req.json()
38
+ while True:
39
+ logger.debug("Kandji device offset: %s", offset)
40
+
41
+ params["offset"] = offset
42
+ response = session.get(api_endpoint, headers=headers, timeout=_TIMEOUT, params=params)
43
+ response.raise_for_status()
44
+
45
+ result = response.json()
46
+ # If no more result, we are done
47
+ if len(result) == 0:
48
+ break
49
+
50
+ devices.extend(result)
51
+
52
+ offset += limit
53
+
54
+ logger.debug("Kandji device count: %d", len(devices))
55
+ return devices
32
56
 
33
57
 
34
58
  @timeit
@@ -0,0 +1,68 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cartography.models.core.common import PropertyRef
4
+ from cartography.models.core.nodes import CartographyNodeProperties
5
+ from cartography.models.core.nodes import CartographyNodeSchema
6
+ from cartography.models.core.nodes import ExtraNodeLabels
7
+ from cartography.models.core.relationships import CartographyRelProperties
8
+ from cartography.models.core.relationships import CartographyRelSchema
9
+ from cartography.models.core.relationships import LinkDirection
10
+ from cartography.models.core.relationships import make_target_node_matcher
11
+ from cartography.models.core.relationships import OtherRelationships
12
+ from cartography.models.core.relationships import TargetNodeMatcher
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class ELBListenerNodeProperties(CartographyNodeProperties):
17
+ id: PropertyRef = PropertyRef('id')
18
+ port: PropertyRef = PropertyRef('port')
19
+ protocol: PropertyRef = PropertyRef('protocol')
20
+ instance_port: PropertyRef = PropertyRef('instance_port')
21
+ instance_protocol: PropertyRef = PropertyRef('instance_protocol')
22
+ policy_names: PropertyRef = PropertyRef('policy_names')
23
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class ELBListenerToLoadBalancerRelProperties(CartographyRelProperties):
28
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class ELBListenerToLoadBalancer(CartographyRelSchema):
33
+ target_node_label: str = 'LoadBalancer'
34
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
35
+ {'id': PropertyRef('LoadBalancerId')},
36
+ )
37
+ direction: LinkDirection = LinkDirection.INWARD
38
+ rel_label: str = "ELB_LISTENER"
39
+ properties: ELBListenerToLoadBalancerRelProperties = ELBListenerToLoadBalancerRelProperties()
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class ELBListenerToAWSAccountRelProperties(CartographyRelProperties):
44
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class ELBListenerToAWSAccount(CartographyRelSchema):
49
+ target_node_label: str = 'AWSAccount'
50
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
51
+ {'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
52
+ )
53
+ direction: LinkDirection = LinkDirection.INWARD
54
+ rel_label: str = "RESOURCE"
55
+ properties: ELBListenerToAWSAccountRelProperties = ELBListenerToAWSAccountRelProperties()
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class ELBListenerSchema(CartographyNodeSchema):
60
+ label: str = 'ELBListener'
61
+ properties: ELBListenerNodeProperties = ELBListenerNodeProperties()
62
+ extra_node_labels: ExtraNodeLabels = ExtraNodeLabels(['Endpoint'])
63
+ sub_resource_relationship: ELBListenerToAWSAccount = ELBListenerToAWSAccount()
64
+ other_relationships: OtherRelationships = OtherRelationships(
65
+ [
66
+ ELBListenerToLoadBalancer(),
67
+ ],
68
+ )
@@ -0,0 +1,102 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cartography.models.core.common import PropertyRef
4
+ from cartography.models.core.nodes import CartographyNodeProperties
5
+ from cartography.models.core.nodes import CartographyNodeSchema
6
+ from cartography.models.core.relationships import CartographyRelProperties
7
+ from cartography.models.core.relationships import CartographyRelSchema
8
+ from cartography.models.core.relationships import LinkDirection
9
+ from cartography.models.core.relationships import make_target_node_matcher
10
+ from cartography.models.core.relationships import OtherRelationships
11
+ from cartography.models.core.relationships import TargetNodeMatcher
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class LoadBalancerNodeProperties(CartographyNodeProperties):
16
+ id: PropertyRef = PropertyRef('id')
17
+ name: PropertyRef = PropertyRef('name')
18
+ dnsname: PropertyRef = PropertyRef('dnsname', extra_index=True)
19
+ canonicalhostedzonename: PropertyRef = PropertyRef('canonicalhostedzonename')
20
+ canonicalhostedzonenameid: PropertyRef = PropertyRef('canonicalhostedzonenameid')
21
+ scheme: PropertyRef = PropertyRef('scheme')
22
+ region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
23
+ createdtime: PropertyRef = PropertyRef('createdtime')
24
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class LoadBalancerToAWSAccountRelProperties(CartographyRelProperties):
29
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class LoadBalancerToAWSAccount(CartographyRelSchema):
34
+ target_node_label: str = 'AWSAccount'
35
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
36
+ {'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
37
+ )
38
+ direction: LinkDirection = LinkDirection.INWARD
39
+ rel_label: str = "RESOURCE"
40
+ properties: LoadBalancerToAWSAccountRelProperties = LoadBalancerToAWSAccountRelProperties()
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class LoadBalancerToSecurityGroupRelProperties(CartographyRelProperties):
45
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class LoadBalancerToSourceSecurityGroup(CartographyRelSchema):
50
+ target_node_label: str = 'EC2SecurityGroup'
51
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
52
+ {'name': PropertyRef('GROUP_NAME')},
53
+ )
54
+ direction: LinkDirection = LinkDirection.OUTWARD
55
+ rel_label: str = "SOURCE_SECURITY_GROUP"
56
+ properties: LoadBalancerToSecurityGroupRelProperties = LoadBalancerToSecurityGroupRelProperties()
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class LoadBalancerToEC2SecurityGroupRelProperties(CartographyRelProperties):
61
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class LoadBalancerToEC2SecurityGroup(CartographyRelSchema):
66
+ target_node_label: str = 'EC2SecurityGroup'
67
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
68
+ {'groupid': PropertyRef('GROUP_IDS', one_to_many=True)},
69
+ )
70
+ direction: LinkDirection = LinkDirection.OUTWARD
71
+ rel_label: str = "MEMBER_OF_EC2_SECURITY_GROUP"
72
+ properties: LoadBalancerToEC2SecurityGroupRelProperties = LoadBalancerToEC2SecurityGroupRelProperties()
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class LoadBalancerToEC2InstanceRelProperties(CartographyRelProperties):
77
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class LoadBalancerToEC2Instance(CartographyRelSchema):
82
+ target_node_label: str = 'EC2Instance'
83
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
84
+ {'instanceid': PropertyRef('INSTANCE_IDS', one_to_many=True)},
85
+ )
86
+ direction: LinkDirection = LinkDirection.OUTWARD
87
+ rel_label: str = "EXPOSE"
88
+ properties: LoadBalancerToEC2InstanceRelProperties = LoadBalancerToEC2InstanceRelProperties()
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class LoadBalancerSchema(CartographyNodeSchema):
93
+ label: str = 'LoadBalancer'
94
+ properties: LoadBalancerNodeProperties = LoadBalancerNodeProperties()
95
+ sub_resource_relationship: LoadBalancerToAWSAccount = LoadBalancerToAWSAccount()
96
+ other_relationships: OtherRelationships = OtherRelationships(
97
+ [
98
+ LoadBalancerToSourceSecurityGroup(),
99
+ LoadBalancerToEC2SecurityGroup(),
100
+ LoadBalancerToEC2Instance(),
101
+ ],
102
+ )
@@ -0,0 +1,87 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cartography.models.core.common import PropertyRef
4
+ from cartography.models.core.nodes import CartographyNodeProperties
5
+ from cartography.models.core.nodes import CartographyNodeSchema
6
+ from cartography.models.core.relationships import CartographyRelProperties
7
+ from cartography.models.core.relationships import CartographyRelSchema
8
+ from cartography.models.core.relationships import LinkDirection
9
+ from cartography.models.core.relationships import make_target_node_matcher
10
+ from cartography.models.core.relationships import OtherRelationships
11
+ from cartography.models.core.relationships import TargetNodeMatcher
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class RouteTableAssociationNodeProperties(CartographyNodeProperties):
16
+ id: PropertyRef = PropertyRef('id')
17
+ route_table_association_id: PropertyRef = PropertyRef('id', extra_index=True)
18
+ target: PropertyRef = PropertyRef('_target')
19
+ gateway_id: PropertyRef = PropertyRef('gateway_id')
20
+ main: PropertyRef = PropertyRef('main')
21
+ route_table_id: PropertyRef = PropertyRef('route_table_id')
22
+ subnet_id: PropertyRef = PropertyRef('subnet_id')
23
+ association_state: PropertyRef = PropertyRef('association_state')
24
+ association_state_message: PropertyRef = PropertyRef('association_state_message')
25
+ region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
26
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class RouteTableAssociationToAwsAccountRelProperties(CartographyRelProperties):
31
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class RouteTableAssociationToAWSAccount(CartographyRelSchema):
36
+ target_node_label: str = 'AWSAccount'
37
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
38
+ {'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
39
+ )
40
+ direction: LinkDirection = LinkDirection.INWARD
41
+ rel_label: str = "RESOURCE"
42
+ properties: RouteTableAssociationToAwsAccountRelProperties = RouteTableAssociationToAwsAccountRelProperties()
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class RouteTableAssociationToSubnetRelProperties(CartographyRelProperties):
47
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class RouteTableAssociationToSubnet(CartographyRelSchema):
52
+ target_node_label: str = 'EC2Subnet'
53
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
54
+ {'subnetid': PropertyRef('subnet_id')},
55
+ )
56
+ direction: LinkDirection = LinkDirection.OUTWARD
57
+ rel_label: str = "ASSOCIATED_SUBNET"
58
+ properties: RouteTableAssociationToSubnetRelProperties = RouteTableAssociationToSubnetRelProperties()
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class RouteTableAssociationToIgwRelProperties(CartographyRelProperties):
63
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class RouteTableAssociationToIgw(CartographyRelSchema):
68
+ target_node_label: str = 'AWSInternetGateway'
69
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
70
+ {'id': PropertyRef('gateway_id')},
71
+ )
72
+ direction: LinkDirection = LinkDirection.OUTWARD
73
+ rel_label: str = "ASSOCIATED_IGW_FOR_INGRESS"
74
+ properties: RouteTableAssociationToIgwRelProperties = RouteTableAssociationToIgwRelProperties()
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class RouteTableAssociationSchema(CartographyNodeSchema):
79
+ label: str = 'EC2RouteTableAssociation'
80
+ properties: RouteTableAssociationNodeProperties = RouteTableAssociationNodeProperties()
81
+ sub_resource_relationship: RouteTableAssociationToAWSAccount = RouteTableAssociationToAWSAccount()
82
+ other_relationships: OtherRelationships = OtherRelationships(
83
+ [
84
+ RouteTableAssociationToSubnet(),
85
+ RouteTableAssociationToIgw(),
86
+ ],
87
+ )
@@ -0,0 +1,121 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cartography.models.core.common import PropertyRef
4
+ from cartography.models.core.nodes import CartographyNodeProperties
5
+ from cartography.models.core.nodes import CartographyNodeSchema
6
+ from cartography.models.core.relationships import CartographyRelProperties
7
+ from cartography.models.core.relationships import CartographyRelSchema
8
+ from cartography.models.core.relationships import LinkDirection
9
+ from cartography.models.core.relationships import make_target_node_matcher
10
+ from cartography.models.core.relationships import OtherRelationships
11
+ from cartography.models.core.relationships import TargetNodeMatcher
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class RouteTableNodeProperties(CartographyNodeProperties):
16
+ """
17
+ Schema describing a RouteTable.
18
+ """
19
+ id: PropertyRef = PropertyRef('id')
20
+ route_table_id: PropertyRef = PropertyRef('route_table_id', extra_index=True)
21
+ owner_id: PropertyRef = PropertyRef('owner_id')
22
+ vpc_id: PropertyRef = PropertyRef('VpcId')
23
+ region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
24
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
25
+ main: PropertyRef = PropertyRef('main')
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class RouteTableToAwsAccountRelProperties(CartographyRelProperties):
30
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class RouteTableToAWSAccount(CartographyRelSchema):
35
+ target_node_label: str = 'AWSAccount'
36
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
37
+ {'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
38
+ )
39
+ direction: LinkDirection = LinkDirection.INWARD
40
+ rel_label: str = "RESOURCE"
41
+ properties: RouteTableToAwsAccountRelProperties = RouteTableToAwsAccountRelProperties()
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class RouteTableToVpcRelProperties(CartographyRelProperties):
46
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class RouteTableToVpc(CartographyRelSchema):
51
+ target_node_label: str = 'AWSVpc'
52
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
53
+ {'id': PropertyRef('vpc_id')},
54
+ )
55
+ direction: LinkDirection = LinkDirection.OUTWARD
56
+ rel_label: str = "MEMBER_OF_AWS_VPC"
57
+ properties: RouteTableToVpcRelProperties = RouteTableToVpcRelProperties()
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class RouteTableToRouteRelProperties(CartographyRelProperties):
62
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class RouteTableToRoute(CartographyRelSchema):
67
+ target_node_label: str = 'EC2Route'
68
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
69
+ {'id': PropertyRef('RouteIds', one_to_many=True)},
70
+ )
71
+ direction: LinkDirection = LinkDirection.OUTWARD
72
+ rel_label: str = "ROUTE"
73
+ properties: RouteTableToRouteRelProperties = RouteTableToRouteRelProperties()
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class RouteTableToAssociationRelProperties(CartographyRelProperties):
78
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class RouteTableToAssociation(CartographyRelSchema):
83
+ target_node_label: str = 'EC2RouteTableAssociation'
84
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
85
+ {'id': PropertyRef('RouteTableAssociationIds', one_to_many=True)},
86
+ )
87
+ direction: LinkDirection = LinkDirection.OUTWARD
88
+ rel_label: str = "ASSOCIATION"
89
+ properties: RouteTableToAssociationRelProperties = RouteTableToAssociationRelProperties()
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class RouteTableToVpnGatewayRelProperties(CartographyRelProperties):
94
+ lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
95
+
96
+
97
+ # TODO implement AWSVpnGateways
98
+ @dataclass(frozen=True)
99
+ class RouteTableToVpnGateway(CartographyRelSchema):
100
+ target_node_label: str = 'AWSVpnGateway'
101
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
102
+ {'id': PropertyRef('VpnGatewayIds', one_to_many=True)},
103
+ )
104
+ direction: LinkDirection = LinkDirection.OUTWARD
105
+ rel_label: str = "CONNECTED_TO"
106
+ properties: RouteTableToVpnGatewayRelProperties = RouteTableToVpnGatewayRelProperties()
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class RouteTableSchema(CartographyNodeSchema):
111
+ label: str = 'EC2RouteTable'
112
+ properties: RouteTableNodeProperties = RouteTableNodeProperties()
113
+ sub_resource_relationship: RouteTableToAWSAccount = RouteTableToAWSAccount()
114
+ other_relationships: OtherRelationships = OtherRelationships(
115
+ [
116
+ RouteTableToVpc(),
117
+ RouteTableToRoute(),
118
+ RouteTableToAssociation(),
119
+ RouteTableToVpnGateway(),
120
+ ],
121
+ )