cartography 0.110.0rc2__py3-none-any.whl → 0.111.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 +16 -3
- cartography/cli.py +46 -0
- cartography/config.py +16 -0
- cartography/data/indexes.cypher +0 -2
- cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
- cartography/graph/querybuilder.py +70 -0
- cartography/intel/aws/apigateway.py +113 -4
- cartography/intel/aws/ec2/vpc.py +140 -124
- cartography/intel/aws/eventbridge.py +73 -0
- cartography/intel/github/repos.py +28 -12
- cartography/intel/github/util.py +12 -0
- cartography/intel/keycloak/__init__.py +153 -0
- cartography/intel/keycloak/authenticationexecutions.py +322 -0
- cartography/intel/keycloak/authenticationflows.py +77 -0
- cartography/intel/keycloak/clients.py +187 -0
- cartography/intel/keycloak/groups.py +126 -0
- cartography/intel/keycloak/identityproviders.py +94 -0
- cartography/intel/keycloak/organizations.py +163 -0
- cartography/intel/keycloak/realms.py +61 -0
- cartography/intel/keycloak/roles.py +202 -0
- cartography/intel/keycloak/scopes.py +73 -0
- cartography/intel/keycloak/users.py +70 -0
- cartography/intel/keycloak/util.py +47 -0
- cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
- cartography/models/aws/ec2/vpc.py +46 -0
- cartography/models/aws/ec2/vpc_cidr.py +102 -0
- cartography/models/aws/eventbridge/target.py +71 -0
- cartography/models/keycloak/__init__.py +0 -0
- cartography/models/keycloak/authenticationexecution.py +160 -0
- cartography/models/keycloak/authenticationflow.py +54 -0
- cartography/models/keycloak/client.py +177 -0
- cartography/models/keycloak/group.py +101 -0
- cartography/models/keycloak/identityprovider.py +89 -0
- cartography/models/keycloak/organization.py +116 -0
- cartography/models/keycloak/organizationdomain.py +73 -0
- cartography/models/keycloak/realm.py +173 -0
- cartography/models/keycloak/role.py +126 -0
- cartography/models/keycloak/scope.py +73 -0
- cartography/models/keycloak/user.py +51 -0
- cartography/models/tailscale/device.py +1 -0
- cartography/sync.py +2 -0
- cartography/util.py +8 -0
- {cartography-0.110.0rc2.dist-info → cartography-0.111.0.dist-info}/METADATA +2 -1
- {cartography-0.110.0rc2.dist-info → cartography-0.111.0.dist-info}/RECORD +53 -25
- cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
- /cartography/models/aws/{__init__.py → apigateway/__init__.py} +0 -0
- /cartography/models/aws/{apigateway.py → apigateway/apigateway.py} +0 -0
- /cartography/models/aws/{apigatewaycertificate.py → apigateway/apigatewaycertificate.py} +0 -0
- /cartography/models/aws/{apigatewayresource.py → apigateway/apigatewayresource.py} +0 -0
- /cartography/models/aws/{apigatewaystage.py → apigateway/apigatewaystage.py} +0 -0
- {cartography-0.110.0rc2.dist-info → cartography-0.111.0.dist-info}/WHEEL +0 -0
- {cartography-0.110.0rc2.dist-info → cartography-0.111.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.110.0rc2.dist-info → cartography-0.111.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.110.0rc2.dist-info → cartography-0.111.0.dist-info}/top_level.txt +0 -0
cartography/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '0.
|
|
21
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.111.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 111, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
cartography/cli.py
CHANGED
|
@@ -762,6 +762,42 @@ class CLI:
|
|
|
762
762
|
"Required if you are using the SentinelOne intel module. Ignored otherwise."
|
|
763
763
|
),
|
|
764
764
|
)
|
|
765
|
+
parser.add_argument(
|
|
766
|
+
"--keycloak-client-id",
|
|
767
|
+
type=str,
|
|
768
|
+
default=None,
|
|
769
|
+
help=(
|
|
770
|
+
"The Keycloak client ID to sync. "
|
|
771
|
+
"Required if you are using the Keycloak intel module. Ignored otherwise."
|
|
772
|
+
),
|
|
773
|
+
)
|
|
774
|
+
parser.add_argument(
|
|
775
|
+
"--keycloak-client-secret-env-var",
|
|
776
|
+
type=str,
|
|
777
|
+
default="KEYCLOAK_CLIENT_SECRET",
|
|
778
|
+
help=(
|
|
779
|
+
"The name of an environment variable containing the Keycloak client secret. "
|
|
780
|
+
"Required if you are using the Keycloak intel module. Ignored otherwise."
|
|
781
|
+
),
|
|
782
|
+
)
|
|
783
|
+
parser.add_argument(
|
|
784
|
+
"--keycloak-url",
|
|
785
|
+
type=str,
|
|
786
|
+
help=(
|
|
787
|
+
"The base URL for the Keycloak instance. "
|
|
788
|
+
"Required if you are using the Keycloak intel module. Ignored otherwise. "
|
|
789
|
+
),
|
|
790
|
+
)
|
|
791
|
+
parser.add_argument(
|
|
792
|
+
"--keycloak-realm",
|
|
793
|
+
type=str,
|
|
794
|
+
default="master",
|
|
795
|
+
help=(
|
|
796
|
+
"The Keycloak realm used for authentication (note: all available realms will be synced). "
|
|
797
|
+
"Should be `master` (default value) in most of the cases. "
|
|
798
|
+
"Required if you are using the Keycloak intel module. Ignored otherwise. "
|
|
799
|
+
),
|
|
800
|
+
)
|
|
765
801
|
|
|
766
802
|
return parser
|
|
767
803
|
|
|
@@ -1133,6 +1169,16 @@ class CLI:
|
|
|
1133
1169
|
else:
|
|
1134
1170
|
config.sentinelone_api_token = None
|
|
1135
1171
|
|
|
1172
|
+
if config.keycloak_client_secret_env_var:
|
|
1173
|
+
logger.debug(
|
|
1174
|
+
f"Reading Client Secret for Keycloak from environment variable {config.keycloak_client_secret_env_var}",
|
|
1175
|
+
)
|
|
1176
|
+
config.keycloak_client_secret = os.environ.get(
|
|
1177
|
+
config.keycloak_client_secret_env_var
|
|
1178
|
+
)
|
|
1179
|
+
else:
|
|
1180
|
+
config.keycloak_client_secret = None
|
|
1181
|
+
|
|
1136
1182
|
# Run cartography
|
|
1137
1183
|
try:
|
|
1138
1184
|
return cartography.sync.run_with_config(self.sync, config)
|
cartography/config.py
CHANGED
|
@@ -166,6 +166,14 @@ class Config:
|
|
|
166
166
|
:param sentinelone_api_token: SentinelOne API token for authentication. Optional.
|
|
167
167
|
:type sentinelone_account_ids: list[str]
|
|
168
168
|
:param sentinelone_account_ids: List of SentinelOne account IDs to sync. Optional.
|
|
169
|
+
:type keycloak_client_id: str
|
|
170
|
+
:param keycloak_client_id: Keycloak client ID for API authentication. Optional.
|
|
171
|
+
:type keycloak_client_secret: str
|
|
172
|
+
:param keycloak_client_secret: Keycloak client secret for API authentication. Optional.
|
|
173
|
+
:type keycloak_realm: str
|
|
174
|
+
:param keycloak_realm: Keycloak realm for authentication (all realms will be synced). Optional.
|
|
175
|
+
:type keycloak_url: str
|
|
176
|
+
:param keycloak_url: Keycloak base URL, e.g. https://keycloak.example.com. Optional.
|
|
169
177
|
"""
|
|
170
178
|
|
|
171
179
|
def __init__(
|
|
@@ -252,6 +260,10 @@ class Config:
|
|
|
252
260
|
sentinelone_api_url=None,
|
|
253
261
|
sentinelone_api_token=None,
|
|
254
262
|
sentinelone_account_ids=None,
|
|
263
|
+
keycloak_client_id=None,
|
|
264
|
+
keycloak_client_secret=None,
|
|
265
|
+
keycloak_realm=None,
|
|
266
|
+
keycloak_url=None,
|
|
255
267
|
):
|
|
256
268
|
self.neo4j_uri = neo4j_uri
|
|
257
269
|
self.neo4j_user = neo4j_user
|
|
@@ -337,3 +349,7 @@ class Config:
|
|
|
337
349
|
self.sentinelone_api_url = sentinelone_api_url
|
|
338
350
|
self.sentinelone_api_token = sentinelone_api_token
|
|
339
351
|
self.sentinelone_account_ids = sentinelone_account_ids
|
|
352
|
+
self.keycloak_client_id = keycloak_client_id
|
|
353
|
+
self.keycloak_client_secret = keycloak_client_secret
|
|
354
|
+
self.keycloak_realm = keycloak_realm
|
|
355
|
+
self.keycloak_url = keycloak_url
|
cartography/data/indexes.cypher
CHANGED
|
@@ -51,8 +51,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:AWSTransitGatewayAttachment) ON (n.lastupdated
|
|
|
51
51
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSUser) ON (n.arn);
|
|
52
52
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSUser) ON (n.name);
|
|
53
53
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSUser) ON (n.lastupdated);
|
|
54
|
-
CREATE INDEX IF NOT EXISTS FOR (n:AWSVpc) ON (n.id);
|
|
55
|
-
CREATE INDEX IF NOT EXISTS FOR (n:AWSVpc) ON (n.lastupdated);
|
|
56
54
|
CREATE INDEX IF NOT EXISTS FOR (n:AccountAccessKey) ON (n.accesskeyid);
|
|
57
55
|
CREATE INDEX IF NOT EXISTS FOR (n:AccountAccessKey) ON (n.lastupdated);
|
|
58
56
|
CREATE INDEX IF NOT EXISTS FOR (n:AutoScalingGroup) ON (n.arn);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"statements": [
|
|
3
|
+
{
|
|
4
|
+
"__comment__": "Inherit group memberships from subgroups to parent groups",
|
|
5
|
+
"query": "MATCH (u:KeycloakUser)-[:MEMBER_OF]->(g:KeycloakGroup)-[:SUBGROUP_OF*1..5]->(pg:KeycloakGroup) MERGE (u)-[r:INHERITED_MEMBER_OF]->(pg) ON CREATE SET r.firstseen = $UPDATE_TAG SET r.lastupdated = $UPDATE_TAG",
|
|
6
|
+
"iterative": false
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"__comment__": "Assign roles to users based on group memberships",
|
|
10
|
+
"query": "MATCH (u:KeycloakUser)-[:MEMBER_OF|INHERITED_MEMBER_OR]->(g:KeycloakGroup)-[:GRANTS]->(r:KeycloakRole) MERGE (u)-[r0:ASSUME_ROLE]-(r) ON CREATE SET r0.firstseen = $UPDATE_TAG SET r0.lastupdated = $UPDATE_TAG",
|
|
11
|
+
"iterative": false
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"__comment__": "Propagate role grants to composite roles",
|
|
15
|
+
"query": "MATCH (r:KeycloakRole)-[:INCLUDES*1..5]->(c:KeycloakRole)-[:GRANTS]->(s:KeycloakScope) MERGE (r)-[r0:INDIRECT_GRANTS]-(s) ON CREATE SET r0.firstseen = $UPDATE_TAG SET r0.lastupdated = $UPDATE_TAG",
|
|
16
|
+
"iterative": false
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"__comment__": "Identify legitimate scopes for users based on assumed roles",
|
|
20
|
+
"query": "MATCH (u:KeycloakUser)-[:ASSUME_ROLE]-(:KeycloakRole)-[:GRANTS|INDIRECT_GRANTS]->(s:KeycloakScope) MERGE (u)-[r:ASSUME_SCOPE]->(s) ON CREATE SET r.firstseen = $UPDATE_TAG SET r.lastupdated = $UPDATE_TAG",
|
|
21
|
+
"iterative": false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"__comment__": "Assign assumed scopes to users for orphan scopes (scopes not granted by any role)",
|
|
25
|
+
"query": "MATCH (s:KeycloakScope)<-[:RESOURCE]-(r:KeycloakRealm) MATCH (u:KeycloakUser)<-[:RESOURCE]-(r) WHERE NOT (s)<-[:GRANTS|INDIRECT_GRANTS]-(:KeycloakRole) MERGE (u)-[r0:ASSUME_SCOPE]->(s) SET r0.firstseen = $UPDATE_TAG SET r0.lastupdated = $UPDATE_TAG",
|
|
26
|
+
"iterative": false
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"name": "Keycloak inheritance analysis"
|
|
30
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import asdict
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version
|
|
3
5
|
from string import Template
|
|
4
6
|
from typing import Dict
|
|
5
7
|
from typing import List
|
|
@@ -223,6 +225,8 @@ def _build_attach_sub_resource_statement(
|
|
|
223
225
|
$RelMergeClause
|
|
224
226
|
ON CREATE SET r.firstseen = timestamp()
|
|
225
227
|
SET
|
|
228
|
+
r._module_name = "$module_name",
|
|
229
|
+
r._module_version = "$module_version",
|
|
226
230
|
$set_rel_properties_statement
|
|
227
231
|
""",
|
|
228
232
|
)
|
|
@@ -244,6 +248,8 @@ def _build_attach_sub_resource_statement(
|
|
|
244
248
|
SubResourceLabel=sub_resource_link.target_node_label,
|
|
245
249
|
MatchClause=_build_match_clause(sub_resource_link.target_node_matcher),
|
|
246
250
|
RelMergeClause=rel_merge_clause,
|
|
251
|
+
module_name=_get_module_from_schema(sub_resource_link),
|
|
252
|
+
module_version=_get_cartography_version(),
|
|
247
253
|
SubResourceRelLabel=sub_resource_link.rel_label,
|
|
248
254
|
set_rel_properties_statement=_build_rel_properties_statement(
|
|
249
255
|
"r",
|
|
@@ -278,6 +284,8 @@ def _build_attach_additional_links_statement(
|
|
|
278
284
|
$RelMerge
|
|
279
285
|
ON CREATE SET $rel_var.firstseen = timestamp()
|
|
280
286
|
SET
|
|
287
|
+
$rel_var._module_name = "$module_name",
|
|
288
|
+
$rel_var._module_version = "$module_version",
|
|
281
289
|
$set_rel_properties_statement
|
|
282
290
|
""",
|
|
283
291
|
)
|
|
@@ -312,6 +320,8 @@ def _build_attach_additional_links_statement(
|
|
|
312
320
|
node_var=node_var,
|
|
313
321
|
rel_var=rel_var,
|
|
314
322
|
RelMerge=rel_merge,
|
|
323
|
+
module_name=_get_module_from_schema(link),
|
|
324
|
+
module_version=_get_cartography_version(),
|
|
315
325
|
set_rel_properties_statement=_build_rel_properties_statement(
|
|
316
326
|
rel_var,
|
|
317
327
|
rel_props_as_dict,
|
|
@@ -453,6 +463,8 @@ def build_ingestion_query(
|
|
|
453
463
|
MERGE (i:$node_label{id: $dict_id_field})
|
|
454
464
|
ON CREATE SET i.firstseen = timestamp()
|
|
455
465
|
SET
|
|
466
|
+
i._module_name = "$module_name",
|
|
467
|
+
i._module_version = "$module_version",
|
|
456
468
|
$set_node_properties_statement
|
|
457
469
|
$attach_relationships_statement
|
|
458
470
|
""",
|
|
@@ -475,6 +487,8 @@ def build_ingestion_query(
|
|
|
475
487
|
ingest_query = query_template.safe_substitute(
|
|
476
488
|
node_label=node_schema.label,
|
|
477
489
|
dict_id_field=node_props.id,
|
|
490
|
+
module_name=_get_module_from_schema(node_schema),
|
|
491
|
+
module_version=_get_cartography_version(),
|
|
478
492
|
set_node_properties_statement=_build_node_properties_statement(
|
|
479
493
|
node_props_as_dict,
|
|
480
494
|
node_schema.extra_node_labels,
|
|
@@ -650,6 +664,8 @@ def build_matchlink_query(rel_schema: CartographyRelSchema) -> str:
|
|
|
650
664
|
MERGE $rel
|
|
651
665
|
ON CREATE SET r.firstseen = timestamp()
|
|
652
666
|
SET
|
|
667
|
+
r._module_name = "$module_name",
|
|
668
|
+
r._module_version = "$module_version",
|
|
653
669
|
$set_rel_properties_statement;
|
|
654
670
|
"""
|
|
655
671
|
)
|
|
@@ -677,8 +693,62 @@ def build_matchlink_query(rel_schema: CartographyRelSchema) -> str:
|
|
|
677
693
|
source_match=source_match,
|
|
678
694
|
target_match=target_match,
|
|
679
695
|
rel=rel,
|
|
696
|
+
module_name=_get_module_from_schema(rel_schema),
|
|
697
|
+
module_version=_get_cartography_version(),
|
|
680
698
|
set_rel_properties_statement=_build_rel_properties_statement(
|
|
681
699
|
"r",
|
|
682
700
|
rel_props_as_dict,
|
|
683
701
|
),
|
|
684
702
|
)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _get_cartography_version() -> str:
|
|
706
|
+
"""
|
|
707
|
+
Get the current version of the cartography package.
|
|
708
|
+
|
|
709
|
+
This function attempts to retrieve the version of the installed cartography package
|
|
710
|
+
using importlib.metadata. If the package is not found (typically in development
|
|
711
|
+
or testing environments), it returns 'dev' as a fallback.
|
|
712
|
+
|
|
713
|
+
Returns:
|
|
714
|
+
The version string of the cartography package, or 'dev' if not found
|
|
715
|
+
"""
|
|
716
|
+
try:
|
|
717
|
+
return version("cartography")
|
|
718
|
+
except PackageNotFoundError:
|
|
719
|
+
# This can occured if the cartography package is not installed in the environment, typically in development or testing environments.
|
|
720
|
+
logger.warning("cartography package not found. Returning 'dev' version.")
|
|
721
|
+
# Fallback to reading the VERSION file if the package is not found
|
|
722
|
+
return "dev"
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _get_module_from_schema(
|
|
726
|
+
schema, #: "CartographyNodeSchema" | "CartographyRelSchema",
|
|
727
|
+
) -> str:
|
|
728
|
+
"""
|
|
729
|
+
Extract the module name from a Cartography schema object.
|
|
730
|
+
|
|
731
|
+
This function extracts and formats the module name from a CartographyNodeSchema
|
|
732
|
+
or CartographyRelSchema object. It expects schemas to be part of the official
|
|
733
|
+
cartography.models package hierarchy and returns a formatted string indicating
|
|
734
|
+
the specific cartography module.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
schema: A CartographyNodeSchema or CartographyRelSchema object
|
|
738
|
+
|
|
739
|
+
Returns:
|
|
740
|
+
A formatted module name string in the format 'cartography:<module_name>'
|
|
741
|
+
or 'unknown:<full_module_path>' if the schema is not from cartography.models
|
|
742
|
+
"""
|
|
743
|
+
# If the entity schema does not belong to the cartography.models package,
|
|
744
|
+
# we log a warning and return the full module path.
|
|
745
|
+
if not schema.__module__.startswith("cartography.models."):
|
|
746
|
+
logger.warning(
|
|
747
|
+
"The schema %s does not start with 'cartography.models.'. "
|
|
748
|
+
"This may indicate that the schema is not part of the official cartography models.",
|
|
749
|
+
schema.__module__,
|
|
750
|
+
)
|
|
751
|
+
return f"unknown:{schema.__module__}"
|
|
752
|
+
# Otherwise, we return the module path as a string.
|
|
753
|
+
parts = schema.__module__.split(".")
|
|
754
|
+
return f"cartography:{parts[2]}"
|
|
@@ -14,12 +14,18 @@ from policyuniverse.policy import Policy
|
|
|
14
14
|
|
|
15
15
|
from cartography.client.core.tx import load
|
|
16
16
|
from cartography.graph.job import GraphJob
|
|
17
|
-
from cartography.
|
|
18
|
-
from cartography.models.aws.
|
|
17
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
18
|
+
from cartography.models.aws.apigateway.apigateway import APIGatewayRestAPISchema
|
|
19
|
+
from cartography.models.aws.apigateway.apigatewaycertificate import (
|
|
19
20
|
APIGatewayClientCertificateSchema,
|
|
20
21
|
)
|
|
21
|
-
from cartography.models.aws.
|
|
22
|
-
|
|
22
|
+
from cartography.models.aws.apigateway.apigatewaydeployment import (
|
|
23
|
+
APIGatewayDeploymentSchema,
|
|
24
|
+
)
|
|
25
|
+
from cartography.models.aws.apigateway.apigatewayresource import (
|
|
26
|
+
APIGatewayResourceSchema,
|
|
27
|
+
)
|
|
28
|
+
from cartography.models.aws.apigateway.apigatewaystage import APIGatewayStageSchema
|
|
23
29
|
from cartography.util import aws_handle_regions
|
|
24
30
|
from cartography.util import timeit
|
|
25
31
|
|
|
@@ -40,6 +46,38 @@ def get_apigateway_rest_apis(
|
|
|
40
46
|
return apis
|
|
41
47
|
|
|
42
48
|
|
|
49
|
+
def get_rest_api_ids(
|
|
50
|
+
rest_apis: List[Dict],
|
|
51
|
+
) -> List[str]:
|
|
52
|
+
"""
|
|
53
|
+
Extracts the IDs of the REST APIs from the provided list.
|
|
54
|
+
"""
|
|
55
|
+
return [api["id"] for api in rest_apis if "id" in api]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@timeit
|
|
59
|
+
@aws_handle_regions
|
|
60
|
+
def get_rest_api_deployments(
|
|
61
|
+
boto3_session: boto3.session.Session,
|
|
62
|
+
rest_api_ids: List[str],
|
|
63
|
+
region: str,
|
|
64
|
+
) -> List[Dict[str, Any]]:
|
|
65
|
+
"""
|
|
66
|
+
Retrieves the deployments for each REST API in the provided list.
|
|
67
|
+
"""
|
|
68
|
+
client = boto3_session.client(
|
|
69
|
+
"apigateway", region_name=region, config=get_botocore_config()
|
|
70
|
+
)
|
|
71
|
+
deployments: List[Dict[str, Any]] = []
|
|
72
|
+
for api_id in rest_api_ids:
|
|
73
|
+
paginator = client.get_paginator("get_deployments")
|
|
74
|
+
for page in paginator.paginate(restApiId=api_id):
|
|
75
|
+
for deployment in page.get("items", []):
|
|
76
|
+
deployment["api_id"] = api_id
|
|
77
|
+
deployments.append(deployment)
|
|
78
|
+
return deployments
|
|
79
|
+
|
|
80
|
+
|
|
43
81
|
@timeit
|
|
44
82
|
@aws_handle_regions
|
|
45
83
|
def get_rest_api_details(
|
|
@@ -63,6 +101,7 @@ def get_rest_api_details(
|
|
|
63
101
|
|
|
64
102
|
|
|
65
103
|
@timeit
|
|
104
|
+
@aws_handle_regions
|
|
66
105
|
def get_rest_api_stages(api: Dict, client: botocore.client.BaseClient) -> Any:
|
|
67
106
|
"""
|
|
68
107
|
Gets the REST API Stage Resources.
|
|
@@ -104,6 +143,7 @@ def get_rest_api_client_certificate(
|
|
|
104
143
|
|
|
105
144
|
|
|
106
145
|
@timeit
|
|
146
|
+
@aws_handle_regions
|
|
107
147
|
def get_rest_api_resources(api: Dict, client: botocore.client.BaseClient) -> List[Any]:
|
|
108
148
|
"""
|
|
109
149
|
Gets the collection of Resource resources.
|
|
@@ -244,6 +284,25 @@ def transform_rest_api_details(
|
|
|
244
284
|
return stages, certificates, resources
|
|
245
285
|
|
|
246
286
|
|
|
287
|
+
def transform_apigateway_deployments(
|
|
288
|
+
deployments: List[Dict[str, Any]],
|
|
289
|
+
region: str,
|
|
290
|
+
) -> List[Dict[str, Any]]:
|
|
291
|
+
"""
|
|
292
|
+
Transform API Gateway Deployment data for ingestion
|
|
293
|
+
"""
|
|
294
|
+
transformed_deployments = []
|
|
295
|
+
for deployment in deployments:
|
|
296
|
+
transformed_deployment = {
|
|
297
|
+
"id": f"{deployment['api_id']}/{deployment['id']}",
|
|
298
|
+
"api_id": deployment["api_id"],
|
|
299
|
+
"description": deployment.get("description"),
|
|
300
|
+
"region": region,
|
|
301
|
+
}
|
|
302
|
+
transformed_deployments.append(transformed_deployment)
|
|
303
|
+
return transformed_deployments
|
|
304
|
+
|
|
305
|
+
|
|
247
306
|
@timeit
|
|
248
307
|
def load_rest_api_details(
|
|
249
308
|
neo4j_session: neo4j.Session,
|
|
@@ -283,6 +342,30 @@ def load_rest_api_details(
|
|
|
283
342
|
)
|
|
284
343
|
|
|
285
344
|
|
|
345
|
+
@timeit
|
|
346
|
+
def load_apigateway_deployments(
|
|
347
|
+
neo4j_session: neo4j.Session,
|
|
348
|
+
data: List[Dict[str, Any]],
|
|
349
|
+
region: str,
|
|
350
|
+
current_aws_account_id: str,
|
|
351
|
+
aws_update_tag: int,
|
|
352
|
+
) -> None:
|
|
353
|
+
"""
|
|
354
|
+
Load API Gateway Deployment data into neo4j.
|
|
355
|
+
"""
|
|
356
|
+
logger.info(
|
|
357
|
+
f"Loading API Gateway {len(data)} deployments for region '{region}' into graph.",
|
|
358
|
+
)
|
|
359
|
+
load(
|
|
360
|
+
neo4j_session,
|
|
361
|
+
APIGatewayDeploymentSchema(),
|
|
362
|
+
data,
|
|
363
|
+
region=region,
|
|
364
|
+
lastupdated=aws_update_tag,
|
|
365
|
+
AWS_ID=current_aws_account_id,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
286
369
|
@timeit
|
|
287
370
|
def parse_policy(api_id: str, policy: Policy) -> Optional[Dict[Any, Any]]:
|
|
288
371
|
"""
|
|
@@ -345,6 +428,12 @@ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
|
345
428
|
)
|
|
346
429
|
cleanup_job.run(neo4j_session)
|
|
347
430
|
|
|
431
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
432
|
+
APIGatewayDeploymentSchema(),
|
|
433
|
+
common_job_parameters,
|
|
434
|
+
)
|
|
435
|
+
cleanup_job.run(neo4j_session)
|
|
436
|
+
|
|
348
437
|
|
|
349
438
|
@timeit
|
|
350
439
|
def sync_apigateway_rest_apis(
|
|
@@ -375,6 +464,19 @@ def sync_apigateway_rest_apis(
|
|
|
375
464
|
current_aws_account_id,
|
|
376
465
|
aws_update_tag,
|
|
377
466
|
)
|
|
467
|
+
|
|
468
|
+
api_ids = get_rest_api_ids(rest_apis)
|
|
469
|
+
deployments = get_rest_api_deployments(
|
|
470
|
+
boto3_session,
|
|
471
|
+
api_ids,
|
|
472
|
+
region,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
transformed_deployments = transform_apigateway_deployments(
|
|
476
|
+
deployments,
|
|
477
|
+
region,
|
|
478
|
+
)
|
|
479
|
+
|
|
378
480
|
load_apigateway_rest_apis(
|
|
379
481
|
neo4j_session,
|
|
380
482
|
transformed_apis,
|
|
@@ -388,6 +490,13 @@ def sync_apigateway_rest_apis(
|
|
|
388
490
|
current_aws_account_id,
|
|
389
491
|
aws_update_tag,
|
|
390
492
|
)
|
|
493
|
+
load_apigateway_deployments(
|
|
494
|
+
neo4j_session,
|
|
495
|
+
transformed_deployments,
|
|
496
|
+
region,
|
|
497
|
+
current_aws_account_id,
|
|
498
|
+
aws_update_tag,
|
|
499
|
+
)
|
|
391
500
|
|
|
392
501
|
|
|
393
502
|
@timeit
|