cartography 0.110.0rc1__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 -8
- cartography/config.py +16 -9
- cartography/data/indexes.cypher +0 -2
- cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -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/cognito.py +201 -0
- cartography/intel/aws/ec2/vpc.py +140 -124
- cartography/intel/aws/ecs.py +7 -1
- cartography/intel/aws/eventbridge.py +73 -0
- cartography/intel/aws/glue.py +64 -0
- cartography/intel/aws/kms.py +13 -1
- cartography/intel/aws/rds.py +105 -0
- cartography/intel/aws/resources.py +2 -0
- cartography/intel/aws/route53.py +3 -1
- cartography/intel/aws/s3.py +104 -0
- cartography/intel/entra/__init__.py +41 -43
- cartography/intel/entra/applications.py +2 -1
- cartography/intel/entra/ou.py +1 -1
- cartography/intel/github/__init__.py +21 -25
- cartography/intel/github/repos.py +32 -48
- 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/intel/kubernetes/__init__.py +4 -0
- cartography/intel/kubernetes/rbac.py +464 -0
- cartography/intel/kubernetes/util.py +17 -0
- cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
- cartography/models/aws/cognito/__init__.py +0 -0
- cartography/models/aws/cognito/identity_pool.py +70 -0
- cartography/models/aws/cognito/user_pool.py +47 -0
- cartography/models/aws/ec2/security_groups.py +1 -1
- cartography/models/aws/ec2/vpc.py +46 -0
- cartography/models/aws/ec2/vpc_cidr.py +102 -0
- cartography/models/aws/ecs/services.py +17 -0
- cartography/models/aws/ecs/tasks.py +1 -0
- cartography/models/aws/eventbridge/target.py +71 -0
- cartography/models/aws/glue/job.py +69 -0
- cartography/models/aws/rds/event_subscription.py +146 -0
- cartography/models/aws/route53/dnsrecord.py +21 -0
- cartography/models/github/dependencies.py +1 -2
- 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/kubernetes/clusterrolebindings.py +98 -0
- cartography/models/kubernetes/clusterroles.py +52 -0
- cartography/models/kubernetes/rolebindings.py +119 -0
- cartography/models/kubernetes/roles.py +76 -0
- cartography/models/kubernetes/serviceaccounts.py +77 -0
- cartography/models/tailscale/device.py +1 -0
- cartography/sync.py +2 -0
- cartography/util.py +8 -0
- {cartography-0.110.0rc1.dist-info → cartography-0.111.0.dist-info}/METADATA +4 -3
- {cartography-0.110.0rc1.dist-info → cartography-0.111.0.dist-info}/RECORD +85 -46
- cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
- cartography/intel/entra/resources.py +0 -20
- /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
- /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.0rc1.dist-info → cartography-0.111.0.dist-info}/WHEEL +0 -0
- {cartography-0.110.0rc1.dist-info → cartography-0.111.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.110.0rc1.dist-info → cartography-0.111.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.110.0rc1.dist-info → cartography-0.111.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
import neo4j
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from cartography.client.core.tx import load
|
|
9
|
+
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.intel.keycloak.util import get_paginated
|
|
11
|
+
from cartography.models.keycloak.role import KeycloakRoleSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
16
|
+
_TIMEOUT = (60, 60)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
def sync(
|
|
21
|
+
neo4j_session: neo4j.Session,
|
|
22
|
+
api_session: requests.Session,
|
|
23
|
+
base_url: str,
|
|
24
|
+
common_job_parameters: dict[str, Any],
|
|
25
|
+
client_ids: list[str],
|
|
26
|
+
scope_ids: list[str],
|
|
27
|
+
) -> None:
|
|
28
|
+
roles = get(api_session, base_url, common_job_parameters["REALM"], client_ids)
|
|
29
|
+
scope_role_mapping = get_mapping(
|
|
30
|
+
api_session,
|
|
31
|
+
base_url,
|
|
32
|
+
common_job_parameters["REALM"],
|
|
33
|
+
scope_ids,
|
|
34
|
+
)
|
|
35
|
+
transformed_roles = transform(roles, scope_role_mapping)
|
|
36
|
+
load_roles(
|
|
37
|
+
neo4j_session,
|
|
38
|
+
transformed_roles,
|
|
39
|
+
common_job_parameters["REALM"],
|
|
40
|
+
common_job_parameters["UPDATE_TAG"],
|
|
41
|
+
)
|
|
42
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@timeit
|
|
46
|
+
def get(
|
|
47
|
+
api_session: requests.Session, base_url: str, realm: str, client_ids: list[str]
|
|
48
|
+
) -> list[dict[str, Any]]:
|
|
49
|
+
roles_by_id: dict[str, dict[str, Any]] = {}
|
|
50
|
+
|
|
51
|
+
# Get roles at the REALM level
|
|
52
|
+
url = f"{base_url}/admin/realms/{realm}/roles"
|
|
53
|
+
for role in get_paginated(api_session, url, params={"briefRepresentation": False}):
|
|
54
|
+
if role.get("composite", False):
|
|
55
|
+
# If the role is composite, we need to get its composites
|
|
56
|
+
composite_roles = get_paginated(
|
|
57
|
+
api_session,
|
|
58
|
+
f"{base_url}/admin/realms/{realm}/roles-by-id/{role['id']}/composites",
|
|
59
|
+
)
|
|
60
|
+
role["_composite_roles"] = [
|
|
61
|
+
composite_role["id"] for composite_role in composite_roles
|
|
62
|
+
]
|
|
63
|
+
# Get the direct members
|
|
64
|
+
direct_members = get_paginated(
|
|
65
|
+
api_session,
|
|
66
|
+
f"{base_url}/admin/realms/{realm}/roles/{quote(role['name'])}/users",
|
|
67
|
+
)
|
|
68
|
+
role["_direct_members"] = [member["id"] for member in direct_members]
|
|
69
|
+
roles_by_id[role["id"]] = role
|
|
70
|
+
|
|
71
|
+
# Get roles for each client
|
|
72
|
+
for client_id in client_ids:
|
|
73
|
+
url = f"{base_url}/admin/realms/{realm}/clients/{client_id}/roles"
|
|
74
|
+
for role in get_paginated(
|
|
75
|
+
api_session, url, params={"briefRepresentation": False}
|
|
76
|
+
):
|
|
77
|
+
# If the role is composite, we need to get its composites
|
|
78
|
+
if role.get("composite", False):
|
|
79
|
+
composite_roles = get_paginated(
|
|
80
|
+
api_session,
|
|
81
|
+
f"{base_url}/admin/realms/{realm}/roles-by-id/{role['id']}/composites",
|
|
82
|
+
)
|
|
83
|
+
role["_composite_roles"] = [
|
|
84
|
+
composite_role["id"] for composite_role in composite_roles
|
|
85
|
+
]
|
|
86
|
+
# Get the direct members
|
|
87
|
+
direct_members = get_paginated(
|
|
88
|
+
api_session,
|
|
89
|
+
f"{base_url}/admin/realms/{realm}/clients/{client_id}/roles/{quote(role['name'])}/users",
|
|
90
|
+
)
|
|
91
|
+
role["_direct_members"] = [member["id"] for member in direct_members]
|
|
92
|
+
roles_by_id[role["id"]] = role
|
|
93
|
+
return list(roles_by_id.values())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@timeit
|
|
97
|
+
def get_mapping(
|
|
98
|
+
api_session: requests.Session,
|
|
99
|
+
base_url: str,
|
|
100
|
+
realm: str,
|
|
101
|
+
scope_ids: list[str],
|
|
102
|
+
) -> dict[str, Any]:
|
|
103
|
+
result: dict[str, Any] = {}
|
|
104
|
+
for scope_id in scope_ids:
|
|
105
|
+
mappings_url = (
|
|
106
|
+
f"{base_url}/admin/realms/{realm}/client-scopes/{scope_id}/scope-mappings"
|
|
107
|
+
)
|
|
108
|
+
req = api_session.get(
|
|
109
|
+
mappings_url,
|
|
110
|
+
timeout=_TIMEOUT,
|
|
111
|
+
)
|
|
112
|
+
req.raise_for_status()
|
|
113
|
+
result[scope_id] = req.json()
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def transform(
|
|
118
|
+
roles: list[dict[str, Any]], scope_role_mapping: dict[str, Any]
|
|
119
|
+
) -> list[dict[str, Any]]:
|
|
120
|
+
transformed_roles = []
|
|
121
|
+
|
|
122
|
+
# Transform the mapping of scopes to roles
|
|
123
|
+
scopes_by_roles: dict[str, list[str]] = {}
|
|
124
|
+
for scope_id, mapping in scope_role_mapping.items():
|
|
125
|
+
for client_details in mapping.get("clientMappings", {}).values():
|
|
126
|
+
for client_mapping in client_details.get("mappings", []):
|
|
127
|
+
scopes_by_roles.setdefault(client_mapping["id"], []).append(scope_id)
|
|
128
|
+
for realm_mapping in mapping.get("realmMappings", []):
|
|
129
|
+
scopes_by_roles.setdefault(realm_mapping["id"], []).append(scope_id)
|
|
130
|
+
|
|
131
|
+
for role in roles:
|
|
132
|
+
role["_scope_ids"] = scopes_by_roles.get(role["id"], None)
|
|
133
|
+
transformed_roles.append(role)
|
|
134
|
+
|
|
135
|
+
return _order_dicts_dfs(
|
|
136
|
+
transformed_roles, children_key="_composite_roles", ignore_unknown_children=True
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@timeit
|
|
141
|
+
def load_roles(
|
|
142
|
+
neo4j_session: neo4j.Session,
|
|
143
|
+
data: list[dict[str, Any]],
|
|
144
|
+
realm: str,
|
|
145
|
+
update_tag: int,
|
|
146
|
+
) -> None:
|
|
147
|
+
logger.info("Loading %d Keycloak Roles (%s) into Neo4j.", len(data), realm)
|
|
148
|
+
load(
|
|
149
|
+
neo4j_session,
|
|
150
|
+
KeycloakRoleSchema(),
|
|
151
|
+
data,
|
|
152
|
+
LASTUPDATED=update_tag,
|
|
153
|
+
REALM=realm,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@timeit
|
|
158
|
+
def cleanup(
|
|
159
|
+
neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
|
|
160
|
+
) -> None:
|
|
161
|
+
GraphJob.from_node_schema(KeycloakRoleSchema(), common_job_parameters).run(
|
|
162
|
+
neo4j_session
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _order_dicts_dfs(
|
|
167
|
+
nodes: list[dict[str, Any]],
|
|
168
|
+
children_key: str = "children",
|
|
169
|
+
ignore_unknown_children: bool = False,
|
|
170
|
+
) -> list[dict[str, Any]]:
|
|
171
|
+
by_id = {n["id"]: n for n in nodes}
|
|
172
|
+
|
|
173
|
+
WHITE, GRAY, BLACK = 0, 1, 2 # unvisited, in stack, done
|
|
174
|
+
state: dict[str, int] = {}
|
|
175
|
+
ordered: list[dict[str, Any]] = []
|
|
176
|
+
|
|
177
|
+
def visit(node_id: str, path: list[str]) -> None:
|
|
178
|
+
s = state.get(node_id, WHITE)
|
|
179
|
+
if s == GRAY:
|
|
180
|
+
cycle = " -> ".join(path + [node_id])
|
|
181
|
+
raise ValueError(f"Cycle detected: {cycle}")
|
|
182
|
+
if s == BLACK:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
state[node_id] = GRAY
|
|
186
|
+
node = by_id[node_id]
|
|
187
|
+
|
|
188
|
+
for child_id in node.get(children_key, []):
|
|
189
|
+
if child_id not in by_id:
|
|
190
|
+
if ignore_unknown_children:
|
|
191
|
+
continue
|
|
192
|
+
raise KeyError(f"Unknown child id: {child_id}")
|
|
193
|
+
visit(child_id, path + [node_id])
|
|
194
|
+
|
|
195
|
+
state[node_id] = BLACK
|
|
196
|
+
ordered.append(node) # post-order: children before parent
|
|
197
|
+
|
|
198
|
+
for nid in by_id:
|
|
199
|
+
if state.get(nid, WHITE) == WHITE:
|
|
200
|
+
visit(nid, [])
|
|
201
|
+
|
|
202
|
+
return ordered
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import neo4j
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from cartography.client.core.tx import load
|
|
8
|
+
from cartography.graph.job import GraphJob
|
|
9
|
+
from cartography.intel.keycloak.util import get_paginated
|
|
10
|
+
from cartography.models.keycloak.scope import KeycloakScopeSchema
|
|
11
|
+
from cartography.util import timeit
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
15
|
+
_TIMEOUT = (60, 60)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@timeit
|
|
19
|
+
def sync(
|
|
20
|
+
neo4j_session: neo4j.Session,
|
|
21
|
+
api_session: requests.Session,
|
|
22
|
+
base_url: str,
|
|
23
|
+
common_job_parameters: dict[str, Any],
|
|
24
|
+
) -> list[dict[str, Any]]:
|
|
25
|
+
scopes = get(
|
|
26
|
+
api_session,
|
|
27
|
+
base_url,
|
|
28
|
+
common_job_parameters["REALM"],
|
|
29
|
+
)
|
|
30
|
+
load_scopes(
|
|
31
|
+
neo4j_session,
|
|
32
|
+
scopes,
|
|
33
|
+
common_job_parameters["REALM"],
|
|
34
|
+
common_job_parameters["UPDATE_TAG"],
|
|
35
|
+
)
|
|
36
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
37
|
+
return scopes
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@timeit
|
|
41
|
+
def get(
|
|
42
|
+
api_session: requests.Session,
|
|
43
|
+
base_url: str,
|
|
44
|
+
realm: str,
|
|
45
|
+
) -> list[dict[str, Any]]:
|
|
46
|
+
url = f"{base_url}/admin/realms/{realm}/client-scopes"
|
|
47
|
+
return list(get_paginated(api_session, url, params={"briefRepresentation": False}))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@timeit
|
|
51
|
+
def load_scopes(
|
|
52
|
+
neo4j_session: neo4j.Session,
|
|
53
|
+
data: list[dict[str, Any]],
|
|
54
|
+
realm: str,
|
|
55
|
+
update_tag: int,
|
|
56
|
+
) -> None:
|
|
57
|
+
logger.info("Loading %d Keycloak Scopes (%s) into Neo4j.", len(data), realm)
|
|
58
|
+
load(
|
|
59
|
+
neo4j_session,
|
|
60
|
+
KeycloakScopeSchema(),
|
|
61
|
+
data,
|
|
62
|
+
LASTUPDATED=update_tag,
|
|
63
|
+
REALM=realm,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@timeit
|
|
68
|
+
def cleanup(
|
|
69
|
+
neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
|
|
70
|
+
) -> None:
|
|
71
|
+
GraphJob.from_node_schema(KeycloakScopeSchema(), common_job_parameters).run(
|
|
72
|
+
neo4j_session
|
|
73
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import neo4j
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from cartography.client.core.tx import load
|
|
8
|
+
from cartography.graph.job import GraphJob
|
|
9
|
+
from cartography.intel.keycloak.util import get_paginated
|
|
10
|
+
from cartography.models.keycloak.user import KeycloakUserSchema
|
|
11
|
+
from cartography.util import timeit
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@timeit
|
|
17
|
+
def sync(
|
|
18
|
+
neo4j_session: neo4j.Session,
|
|
19
|
+
api_session: requests.Session,
|
|
20
|
+
base_url: str,
|
|
21
|
+
common_job_parameters: dict[str, Any],
|
|
22
|
+
) -> None:
|
|
23
|
+
users = get(
|
|
24
|
+
api_session,
|
|
25
|
+
base_url,
|
|
26
|
+
common_job_parameters["REALM"],
|
|
27
|
+
)
|
|
28
|
+
load_users(
|
|
29
|
+
neo4j_session,
|
|
30
|
+
users,
|
|
31
|
+
common_job_parameters["REALM"],
|
|
32
|
+
common_job_parameters["UPDATE_TAG"],
|
|
33
|
+
)
|
|
34
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@timeit
|
|
38
|
+
def get(
|
|
39
|
+
api_session: requests.Session,
|
|
40
|
+
base_url: str,
|
|
41
|
+
realm: str,
|
|
42
|
+
) -> list[dict[str, Any]]:
|
|
43
|
+
url = f"{base_url}/admin/realms/{realm}/users"
|
|
44
|
+
return list(get_paginated(api_session, url))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@timeit
|
|
48
|
+
def load_users(
|
|
49
|
+
neo4j_session: neo4j.Session,
|
|
50
|
+
data: list[dict[str, Any]],
|
|
51
|
+
realm: str,
|
|
52
|
+
update_tag: int,
|
|
53
|
+
) -> None:
|
|
54
|
+
logger.info("Loading %d Keycloak Users (%s) into Neo4j.", len(data), realm)
|
|
55
|
+
load(
|
|
56
|
+
neo4j_session,
|
|
57
|
+
KeycloakUserSchema(),
|
|
58
|
+
data,
|
|
59
|
+
LASTUPDATED=update_tag,
|
|
60
|
+
REALM=realm,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@timeit
|
|
65
|
+
def cleanup(
|
|
66
|
+
neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
|
|
67
|
+
) -> None:
|
|
68
|
+
GraphJob.from_node_schema(KeycloakUserSchema(), common_job_parameters).run(
|
|
69
|
+
neo4j_session
|
|
70
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
7
|
+
_TIMEOUT = (60, 60)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_paginated(
|
|
11
|
+
api_session: requests.Session,
|
|
12
|
+
endpoint: str,
|
|
13
|
+
items_per_page: int = 100,
|
|
14
|
+
params: dict[str, Any] | None = None,
|
|
15
|
+
) -> Generator[dict[str, Any], None, None]:
|
|
16
|
+
"""Fetch paginated results from a REST API endpoint.
|
|
17
|
+
|
|
18
|
+
This function handles pagination by making multiple requests to the API
|
|
19
|
+
until all pages of results have been retrieved.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
api_session (requests.Session): The requests session to use for making API calls.
|
|
23
|
+
endpoint (str): The API endpoint to fetch data from.
|
|
24
|
+
items_per_page (int, optional): The number of items to retrieve per page. Defaults to 100.
|
|
25
|
+
params (dict[str, Any] | None, optional): Additional query parameters to include in the request. Defaults to None.
|
|
26
|
+
|
|
27
|
+
Yields:
|
|
28
|
+
Generator[dict[str, Any], None, None]: A generator that yields the individual items from the paginated response.
|
|
29
|
+
"""
|
|
30
|
+
has_more = True
|
|
31
|
+
offset = 0
|
|
32
|
+
while has_more:
|
|
33
|
+
if params is None:
|
|
34
|
+
payload = {}
|
|
35
|
+
else:
|
|
36
|
+
payload = params.copy()
|
|
37
|
+
payload["first"] = offset
|
|
38
|
+
payload["max"] = items_per_page
|
|
39
|
+
req = api_session.get(endpoint, params=payload, timeout=_TIMEOUT)
|
|
40
|
+
req.raise_for_status()
|
|
41
|
+
data = req.json()
|
|
42
|
+
if not data:
|
|
43
|
+
break
|
|
44
|
+
yield from data
|
|
45
|
+
if len(data) < items_per_page:
|
|
46
|
+
has_more = False
|
|
47
|
+
offset += len(data)
|
|
@@ -6,6 +6,7 @@ from cartography.config import Config
|
|
|
6
6
|
from cartography.intel.kubernetes.clusters import sync_kubernetes_cluster
|
|
7
7
|
from cartography.intel.kubernetes.namespaces import sync_namespaces
|
|
8
8
|
from cartography.intel.kubernetes.pods import sync_pods
|
|
9
|
+
from cartography.intel.kubernetes.rbac import sync_kubernetes_rbac
|
|
9
10
|
from cartography.intel.kubernetes.secrets import sync_secrets
|
|
10
11
|
from cartography.intel.kubernetes.services import sync_services
|
|
11
12
|
from cartography.intel.kubernetes.util import get_k8s_clients
|
|
@@ -38,6 +39,9 @@ def start_k8s_ingestion(session: Session, config: Config) -> None:
|
|
|
38
39
|
common_job_parameters["CLUSTER_ID"] = cluster_info.get("id")
|
|
39
40
|
|
|
40
41
|
sync_namespaces(session, client, config.update_tag, common_job_parameters)
|
|
42
|
+
sync_kubernetes_rbac(
|
|
43
|
+
session, client, config.update_tag, common_job_parameters
|
|
44
|
+
)
|
|
41
45
|
all_pods = sync_pods(
|
|
42
46
|
session,
|
|
43
47
|
client,
|