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.
- cartography/_version.py +2 -2
- cartography/cli.py +61 -0
- cartography/config.py +16 -0
- cartography/data/indexes.cypher +0 -6
- cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json +0 -5
- cartography/intel/aws/__init__.py +11 -1
- cartography/intel/aws/ec2/launch_templates.py +14 -5
- cartography/intel/aws/ec2/load_balancers.py +126 -148
- cartography/intel/aws/ec2/route_tables.py +287 -0
- cartography/intel/aws/resources.py +2 -0
- cartography/intel/aws/util/common.py +27 -0
- cartography/intel/crowdstrike/__init__.py +17 -5
- cartography/intel/crowdstrike/endpoints.py +12 -44
- cartography/intel/entra/__init__.py +43 -0
- cartography/intel/entra/users.py +205 -0
- cartography/intel/kandji/devices.py +27 -3
- cartography/models/aws/ec2/load_balancer_listeners.py +68 -0
- cartography/models/aws/ec2/load_balancers.py +102 -0
- cartography/models/aws/ec2/route_table_associations.py +87 -0
- cartography/models/aws/ec2/route_tables.py +121 -0
- cartography/models/aws/ec2/routes.py +77 -0
- cartography/models/crowdstrike/__init__.py +0 -0
- cartography/models/crowdstrike/hosts.py +49 -0
- cartography/models/entra/__init__.py +0 -0
- cartography/models/entra/tenant.py +33 -0
- cartography/models/entra/user.py +83 -0
- cartography/stats.py +1 -1
- cartography/sync.py +2 -0
- {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/METADATA +4 -1
- {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/RECORD +34 -21
- {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/WHEEL +1 -1
- {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
)
|