cartography 0.90.0rc2__py3-none-any.whl → 0.91.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/cli.py +44 -0
- cartography/config.py +12 -0
- cartography/intel/aws/ec2/images.py +9 -9
- cartography/intel/aws/ec2/snapshots.py +4 -2
- cartography/intel/github/teams.py +6 -3
- cartography/intel/github/util.py +26 -8
- cartography/intel/kandji/__init__.py +39 -0
- cartography/intel/kandji/devices.py +84 -0
- cartography/models/kandji/__init__.py +0 -0
- cartography/models/kandji/device.py +48 -0
- cartography/models/kandji/tenant.py +17 -0
- cartography/sync.py +2 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/METADATA +1 -1
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/RECORD +19 -14
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/WHEEL +1 -1
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/LICENSE +0 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/NOTICE +0 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.91.0.dist-info}/top_level.txt +0 -0
cartography/cli.py
CHANGED
|
@@ -325,6 +325,30 @@ class CLI:
|
|
|
325
325
|
default=None,
|
|
326
326
|
help='The name of an environment variable containing a password with which to authenticate to Jamf.',
|
|
327
327
|
)
|
|
328
|
+
parser.add_argument(
|
|
329
|
+
'--kandji-base-uri',
|
|
330
|
+
type=str,
|
|
331
|
+
default=None,
|
|
332
|
+
help=(
|
|
333
|
+
'Your Kandji base URI, e.g. https://company.api.kandji.io.'
|
|
334
|
+
'Required if you are using the Kandji intel module. Ignored otherwise.'
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
parser.add_argument(
|
|
338
|
+
'--kandji-tenant-id',
|
|
339
|
+
type=str,
|
|
340
|
+
default=None,
|
|
341
|
+
help=(
|
|
342
|
+
'Your Kandji tenant id e.g. company.'
|
|
343
|
+
'Required using the Kandji intel module. Ignored otherwise.'
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
parser.add_argument(
|
|
347
|
+
'--kandji-token-env-var',
|
|
348
|
+
type=str,
|
|
349
|
+
default=None,
|
|
350
|
+
help='The name of an environment variable containing token with which to authenticate to Kandji.',
|
|
351
|
+
)
|
|
328
352
|
parser.add_argument(
|
|
329
353
|
'--k8s-kubeconfig',
|
|
330
354
|
default=None,
|
|
@@ -620,6 +644,26 @@ class CLI:
|
|
|
620
644
|
config.jamf_user = None
|
|
621
645
|
config.jamf_password = None
|
|
622
646
|
|
|
647
|
+
# Kandji config
|
|
648
|
+
if config.kandji_base_uri:
|
|
649
|
+
if config.kandji_token_env_var:
|
|
650
|
+
logger.debug(
|
|
651
|
+
"Reading Kandji API token from environment variable '%s'.",
|
|
652
|
+
config.kandji_token_env_var,
|
|
653
|
+
)
|
|
654
|
+
config.kandji_token = os.environ.get(config.kandji_token_env_var)
|
|
655
|
+
elif os.environ.get('KANDJI_TOKEN'):
|
|
656
|
+
logger.debug(
|
|
657
|
+
"Reading Kandji API token from environment variable 'KANDJI_TOKEN'.",
|
|
658
|
+
)
|
|
659
|
+
config.kandji_token = os.environ.get('KANDJI_TOKEN')
|
|
660
|
+
else:
|
|
661
|
+
logger.warning("A Kandji base URI was provided but a token was not.")
|
|
662
|
+
config.kandji_token = None
|
|
663
|
+
else:
|
|
664
|
+
logger.warning("A Kandji base URI was not provided.")
|
|
665
|
+
config.kandji_base_uri = None
|
|
666
|
+
|
|
623
667
|
if config.statsd_enabled:
|
|
624
668
|
logger.debug(
|
|
625
669
|
f'statsd enabled. Sending metrics to server {config.statsd_host}:{config.statsd_port}. '
|
cartography/config.py
CHANGED
|
@@ -69,6 +69,12 @@ class Config:
|
|
|
69
69
|
:param jamf_user: User name used to authenticate to the Jamf data provider. Optional.
|
|
70
70
|
:type jamf_password: string
|
|
71
71
|
:param jamf_password: Password used to authenticate to the Jamf data provider. Optional.
|
|
72
|
+
:type kandji_base_uri: string
|
|
73
|
+
:param kandji_base_uri: Kandji data provider base URI, e.g. https://company.api.kandji.io. Optional.
|
|
74
|
+
:type kandji_tenant_id: string
|
|
75
|
+
:param kandji_tenant_id: Kandji tenant id. e.g. company Optional.
|
|
76
|
+
:type kandji_token: string
|
|
77
|
+
:param kandji_token: Token used to authenticate to the Kandji data provider. Optional.
|
|
72
78
|
:type statsd_enabled: bool
|
|
73
79
|
:param statsd_enabled: Whether to collect statsd metrics such as sync execution times. Optional.
|
|
74
80
|
:type statsd_host: str
|
|
@@ -137,6 +143,9 @@ class Config:
|
|
|
137
143
|
jamf_base_uri=None,
|
|
138
144
|
jamf_user=None,
|
|
139
145
|
jamf_password=None,
|
|
146
|
+
kandji_base_uri=None,
|
|
147
|
+
kandji_tenant_id=None,
|
|
148
|
+
kandji_token=None,
|
|
140
149
|
k8s_kubeconfig=None,
|
|
141
150
|
statsd_enabled=False,
|
|
142
151
|
statsd_prefix=None,
|
|
@@ -190,6 +199,9 @@ class Config:
|
|
|
190
199
|
self.jamf_base_uri = jamf_base_uri
|
|
191
200
|
self.jamf_user = jamf_user
|
|
192
201
|
self.jamf_password = jamf_password
|
|
202
|
+
self.kandji_base_uri = kandji_base_uri
|
|
203
|
+
self.kandji_tenant_id = kandji_tenant_id
|
|
204
|
+
self.kandji_token = kandji_token
|
|
193
205
|
self.k8s_kubeconfig = k8s_kubeconfig
|
|
194
206
|
self.statsd_enabled = statsd_enabled
|
|
195
207
|
self.statsd_prefix = statsd_prefix
|
|
@@ -19,23 +19,23 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
|
|
20
20
|
@timeit
|
|
21
21
|
def get_images_in_use(neo4j_session: neo4j.Session, region: str, current_aws_account_id: str) -> List[str]:
|
|
22
|
-
# We use OPTIONAL here to allow query chaining with queries that may not match.
|
|
23
22
|
get_images_query = """
|
|
24
|
-
|
|
23
|
+
MATCH (:AWSAccount{id: $AWS_ACCOUNT_ID})-[:RESOURCE]->(i:EC2Instance)
|
|
25
24
|
WHERE i.region = $Region
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
RETURN DISTINCT(i.imageid) as image
|
|
26
|
+
UNION
|
|
27
|
+
MATCH (:AWSAccount{id: $AWS_ACCOUNT_ID})-[:RESOURCE]->(lc:LaunchConfiguration)
|
|
28
28
|
WHERE lc.region = $Region
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
RETURN DISTINCT(lc.image_id) as image
|
|
30
|
+
UNION
|
|
31
|
+
MATCH (:AWSAccount{id: $AWS_ACCOUNT_ID})-[:RESOURCE]->(ltv:LaunchTemplateVersion)
|
|
31
32
|
WHERE ltv.region = $Region
|
|
32
|
-
|
|
33
|
-
RETURN images
|
|
33
|
+
RETURN DISTINCT(ltv.image_id) as image
|
|
34
34
|
"""
|
|
35
35
|
results = neo4j_session.run(get_images_query, AWS_ACCOUNT_ID=current_aws_account_id, Region=region)
|
|
36
36
|
images = []
|
|
37
37
|
for r in results:
|
|
38
|
-
images.
|
|
38
|
+
images.append(r['image'])
|
|
39
39
|
return images
|
|
40
40
|
|
|
41
41
|
|
|
@@ -42,8 +42,10 @@ def get_snapshots(boto3_session: boto3.session.Session, region: str, in_use_snap
|
|
|
42
42
|
snapshots.extend(page['Snapshots'])
|
|
43
43
|
except ClientError as e:
|
|
44
44
|
if e.response['Error']['Code'] == 'InvalidSnapshot.NotFound':
|
|
45
|
-
logger.warning(
|
|
46
|
-
|
|
45
|
+
logger.warning(
|
|
46
|
+
f"Failed to retrieve page of in-use, \
|
|
47
|
+
not owned snapshots. Continuing anyway. Error - {e}",
|
|
48
|
+
)
|
|
47
49
|
else:
|
|
48
50
|
raise
|
|
49
51
|
|
|
@@ -57,10 +57,13 @@ def _get_team_repos_for_multiple_teams(
|
|
|
57
57
|
|
|
58
58
|
team_repos = _get_team_repos(org, api_url, token, team_name) if repo_count > 0 else None
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
repo_urls = []
|
|
61
|
+
repo_permissions = []
|
|
62
|
+
if team_repos:
|
|
63
|
+
repo_urls = [t['url'] for t in team_repos.nodes] if team_repos.nodes else []
|
|
64
|
+
repo_permissions = [t['permission'] for t in team_repos.edges] if team_repos.edges else []
|
|
63
65
|
|
|
66
|
+
# Shape = [(repo_url, 'WRITE'), ...]]
|
|
64
67
|
result[team_name] = list(zip(repo_urls, repo_permissions))
|
|
65
68
|
return result
|
|
66
69
|
|
cartography/intel/github/util.py
CHANGED
|
@@ -81,12 +81,12 @@ def call_github_api(query: str, variables: str, token: str, api_url: str) -> Dic
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def fetch_page(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
token: str,
|
|
85
|
+
api_url: str,
|
|
86
|
+
organization: str,
|
|
87
|
+
query: str,
|
|
88
|
+
cursor: Optional[str] = None,
|
|
89
|
+
**kwargs: Any,
|
|
90
90
|
) -> Dict[str, Any]:
|
|
91
91
|
"""
|
|
92
92
|
Return a single page of max size 100 elements from the Github api_url using the given `query` and `cursor` params.
|
|
@@ -139,6 +139,7 @@ def fetch_all(
|
|
|
139
139
|
"""
|
|
140
140
|
cursor = None
|
|
141
141
|
has_next_page = True
|
|
142
|
+
org_data: Dict[str, Any] = {}
|
|
142
143
|
data: PaginatedGraphqlData = PaginatedGraphqlData(nodes=[], edges=[])
|
|
143
144
|
retry = 0
|
|
144
145
|
|
|
@@ -170,6 +171,15 @@ def fetch_all(
|
|
|
170
171
|
time.sleep(2 ** retry)
|
|
171
172
|
continue
|
|
172
173
|
|
|
174
|
+
if 'data' not in resp:
|
|
175
|
+
logger.warning(
|
|
176
|
+
f'Got no "data" attribute in response: {resp}. '
|
|
177
|
+
f'Stopping requests for organization: {organization} and '
|
|
178
|
+
f'resource_type: {resource_type}',
|
|
179
|
+
)
|
|
180
|
+
has_next_page = False
|
|
181
|
+
continue
|
|
182
|
+
|
|
173
183
|
resource = resp['data']['organization'][resource_type]
|
|
174
184
|
if resource_inner_type:
|
|
175
185
|
resource = resp['data']['organization'][resource_type][resource_inner_type]
|
|
@@ -180,6 +190,14 @@ def fetch_all(
|
|
|
180
190
|
|
|
181
191
|
cursor = resource['pageInfo']['endCursor']
|
|
182
192
|
has_next_page = resource['pageInfo']['hasNextPage']
|
|
183
|
-
|
|
184
|
-
|
|
193
|
+
if not org_data:
|
|
194
|
+
org_data = {
|
|
195
|
+
'url': resp['data']['organization']['url'],
|
|
196
|
+
'login': resp['data']['organization']['login'],
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if not org_data:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"Didn't get any organization data for organization: {organization} and resource_type: {resource_type}",
|
|
202
|
+
)
|
|
185
203
|
return data, org_data
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import neo4j
|
|
4
|
+
|
|
5
|
+
import cartography.intel.kandji.devices
|
|
6
|
+
from cartography.config import Config
|
|
7
|
+
from cartography.util import timeit
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@timeit
|
|
13
|
+
def start_kandji_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
14
|
+
"""
|
|
15
|
+
If this module is configured, perform ingestion of Kandji devices. Otherwise warn and exit
|
|
16
|
+
|
|
17
|
+
:param neo4j_session: Neo4J session for database interface
|
|
18
|
+
:param config: A cartography.config object
|
|
19
|
+
|
|
20
|
+
:return: None
|
|
21
|
+
"""
|
|
22
|
+
if config.kandji_base_uri is None or config.kandji_token is None or config.kandji_tenant_id is None:
|
|
23
|
+
logger.warning(
|
|
24
|
+
'Required parameter(s) missing. Skipping sync.',
|
|
25
|
+
'See docs to configure.',
|
|
26
|
+
)
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
common_job_parameters = {
|
|
30
|
+
"UPDATE_TAG": config.update_tag,
|
|
31
|
+
"TENANT_ID": config.kandji_tenant_id,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
cartography.intel.kandji.devices.sync(
|
|
35
|
+
neo4j_session,
|
|
36
|
+
config.kandji_base_uri,
|
|
37
|
+
config.kandji_token,
|
|
38
|
+
common_job_parameters=common_job_parameters,
|
|
39
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
from requests import Session
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.kandji.device import KandjiDeviceSchema
|
|
12
|
+
from cartography.models.kandji.tenant import KandjiTenantSchema
|
|
13
|
+
from cartography.util import timeit
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
_TIMEOUT = (60, 60)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@timeit
|
|
21
|
+
def get(kandji_base_uri: str, kandji_token: str) -> List[Dict[str, Any]]:
|
|
22
|
+
api_endpoint = f"{kandji_base_uri}/api/v1/devices"
|
|
23
|
+
headers = {
|
|
24
|
+
'Accept': 'application/json',
|
|
25
|
+
'Authorization': f'Bearer {kandji_token}',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
session = Session()
|
|
29
|
+
req = session.get(api_endpoint, headers=headers, timeout=_TIMEOUT)
|
|
30
|
+
req.raise_for_status()
|
|
31
|
+
return req.json()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@timeit
|
|
35
|
+
def transform(api_result: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
36
|
+
result: List[Dict[str, Any]] = []
|
|
37
|
+
for device in api_result:
|
|
38
|
+
n_device = device
|
|
39
|
+
n_device['id'] = device['device_id']
|
|
40
|
+
result.append(n_device)
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@timeit
|
|
45
|
+
def load_devices(
|
|
46
|
+
neo4j_session: neo4j.Session,
|
|
47
|
+
common_job_parameters: Dict[str, Any],
|
|
48
|
+
data: List[Dict[str, Any]],
|
|
49
|
+
) -> None:
|
|
50
|
+
|
|
51
|
+
tenant_id = common_job_parameters["TENANT_ID"]
|
|
52
|
+
update_tag = common_job_parameters["UPDATE_TAG"]
|
|
53
|
+
|
|
54
|
+
load(
|
|
55
|
+
neo4j_session,
|
|
56
|
+
KandjiTenantSchema(),
|
|
57
|
+
[{'id': tenant_id}],
|
|
58
|
+
lastupdated=update_tag,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
load(
|
|
62
|
+
neo4j_session,
|
|
63
|
+
KandjiDeviceSchema(),
|
|
64
|
+
data,
|
|
65
|
+
lastupdated=update_tag,
|
|
66
|
+
TENANT_ID=tenant_id,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
|
|
71
|
+
GraphJob.from_node_schema(KandjiDeviceSchema(), common_job_parameters).run(neo4j_session)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@timeit
|
|
75
|
+
def sync(
|
|
76
|
+
neo4j_session: neo4j.Session,
|
|
77
|
+
kandji_base_uri: str,
|
|
78
|
+
kandji_token: str,
|
|
79
|
+
common_job_parameters: Dict[str, Any],
|
|
80
|
+
) -> None:
|
|
81
|
+
devices = get(kandji_base_uri=kandji_base_uri, kandji_token=kandji_token)
|
|
82
|
+
formatted_devices = transform(devices)
|
|
83
|
+
load_devices(neo4j_session, common_job_parameters, formatted_devices)
|
|
84
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
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 TargetNodeMatcher
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class KandjiDeviceNodeProperties(CartographyNodeProperties):
|
|
15
|
+
id: PropertyRef = PropertyRef('id')
|
|
16
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
17
|
+
|
|
18
|
+
device_id: PropertyRef = PropertyRef('device_id')
|
|
19
|
+
device_name: PropertyRef = PropertyRef('device_name')
|
|
20
|
+
last_check_in: PropertyRef = PropertyRef('last_check_in')
|
|
21
|
+
model: PropertyRef = PropertyRef('model')
|
|
22
|
+
os_version: PropertyRef = PropertyRef('os_version')
|
|
23
|
+
platform: PropertyRef = PropertyRef('platform')
|
|
24
|
+
serial_number: PropertyRef = PropertyRef('serial_number', extra_index=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class KandjiTenantToKandjiDeviceRelProperties(CartographyRelProperties):
|
|
29
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
# (:KandjiDevice)-[:ENROLLED_TO]->(:KandjiTenant)
|
|
34
|
+
class KandjiTenantToKandjiDeviceRel(CartographyRelSchema):
|
|
35
|
+
target_node_label: str = 'KandjiTenant'
|
|
36
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
37
|
+
{'id': PropertyRef('TENANT_ID', set_in_kwargs=True)},
|
|
38
|
+
)
|
|
39
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
40
|
+
rel_label: str = "ENROLLED_TO"
|
|
41
|
+
properties: KandjiTenantToKandjiDeviceRelProperties = KandjiTenantToKandjiDeviceRelProperties()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class KandjiDeviceSchema(CartographyNodeSchema):
|
|
46
|
+
label: str = 'KandjiDevice' # The label of the node
|
|
47
|
+
properties: KandjiDeviceNodeProperties = KandjiDeviceNodeProperties() # An object representing all properties
|
|
48
|
+
sub_resource_relationship: KandjiTenantToKandjiDeviceRel = KandjiTenantToKandjiDeviceRel()
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class KandjiTenantNodeProperties(CartographyNodeProperties):
|
|
10
|
+
id: PropertyRef = PropertyRef('id')
|
|
11
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class KandjiTenantSchema(CartographyNodeSchema):
|
|
16
|
+
label: str = 'KandjiTenant' # The label of the node
|
|
17
|
+
properties: KandjiTenantNodeProperties = KandjiTenantNodeProperties() # An object representing all properties
|
cartography/sync.py
CHANGED
|
@@ -24,6 +24,7 @@ import cartography.intel.duo
|
|
|
24
24
|
import cartography.intel.gcp
|
|
25
25
|
import cartography.intel.github
|
|
26
26
|
import cartography.intel.gsuite
|
|
27
|
+
import cartography.intel.kandji
|
|
27
28
|
import cartography.intel.kubernetes
|
|
28
29
|
import cartography.intel.lastpass
|
|
29
30
|
import cartography.intel.oci
|
|
@@ -50,6 +51,7 @@ TOP_LEVEL_MODULES = OrderedDict({ # preserve order so that the default sync alw
|
|
|
50
51
|
'okta': cartography.intel.okta.start_okta_ingestion,
|
|
51
52
|
'github': cartography.intel.github.start_github_ingestion,
|
|
52
53
|
'digitalocean': cartography.intel.digitalocean.start_digitalocean_ingestion,
|
|
54
|
+
'kandji': cartography.intel.kandji.start_kandji_ingestion,
|
|
53
55
|
'kubernetes': cartography.intel.kubernetes.start_k8s_ingestion,
|
|
54
56
|
'lastpass': cartography.intel.lastpass.start_lastpass_ingestion,
|
|
55
57
|
'bigfix': cartography.intel.bigfix.start_bigfix_ingestion,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
cartography/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
cartography/__main__.py,sha256=JftXT_nUPkqcEh8uxCCT4n-OyHYqbldEgrDS-4ygy0U,101
|
|
3
|
-
cartography/cli.py,sha256=
|
|
4
|
-
cartography/config.py,sha256=
|
|
3
|
+
cartography/cli.py,sha256=ot9_gMxw5_irVS7KYfWf5HIr2Xkb10RDEbOTY1nzUcw,31787
|
|
4
|
+
cartography/config.py,sha256=rL1zgxZO47_R7S6E9e0CwxmhzRSN0X_q93NtcPR1G00,11368
|
|
5
5
|
cartography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
cartography/stats.py,sha256=dbybb9V2FuvSuHjjNwz6Vjwnd1hap2C7h960rLoKcl8,4406
|
|
7
|
-
cartography/sync.py,sha256=
|
|
7
|
+
cartography/sync.py,sha256=a80r_IzrZcWGSmRDRrxkesNYPiOuLte5YHvDQT3L-Lw,9730
|
|
8
8
|
cartography/util.py,sha256=F3FPMJl1KDW0x_5cvt2ZGI0Dv1LVrHU7Az4OleAANBI,14474
|
|
9
9
|
cartography/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
cartography/client/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -172,7 +172,7 @@ cartography/intel/aws/ssm.py,sha256=IDOYa8v2FgziU8nBOZ7wyDG4o_nFYshbB-si9Ut_9Zc,
|
|
|
172
172
|
cartography/intel/aws/ec2/__init__.py,sha256=IDK2Yap7mllK_ab6yVMLXatJ94znIkn-szv5RJP5fbo,346
|
|
173
173
|
cartography/intel/aws/ec2/auto_scaling_groups.py,sha256=4erjP31KSVW-Pp2ASmDox_VLp_AQUAin4KYxfZKZcSM,9223
|
|
174
174
|
cartography/intel/aws/ec2/elastic_ip_addresses.py,sha256=0k4NwS73VyWbEj5jXvSkaq2RNvmAlBlrN-UKa_Bj0uk,3957
|
|
175
|
-
cartography/intel/aws/ec2/images.py,sha256=
|
|
175
|
+
cartography/intel/aws/ec2/images.py,sha256=DcGHJwZf8_K5iRCDJ2QOdP837TTJTwST02IRswfk9Mc,3697
|
|
176
176
|
cartography/intel/aws/ec2/instances.py,sha256=mnTjdBY-4D-TGhH29UrSaLUW0Uft0JApDIJkkLz4zPc,12170
|
|
177
177
|
cartography/intel/aws/ec2/internet_gateways.py,sha256=dI-4-85_3DGGZZBcY_DN6XqESx9P26S6jKok314lcnQ,2883
|
|
178
178
|
cartography/intel/aws/ec2/key_pairs.py,sha256=SvRgd56vE4eouvTSNoFK8PP8HYoECO91goxc36oq_FY,2508
|
|
@@ -182,7 +182,7 @@ cartography/intel/aws/ec2/load_balancers.py,sha256=1GwErzGqi3BKCARqfGJcD_r_D84rF
|
|
|
182
182
|
cartography/intel/aws/ec2/network_interfaces.py,sha256=CzF8PooCYUQ2pk8DR8JDAhkWRUQSBj_27OsIfkL_-Cs,9199
|
|
183
183
|
cartography/intel/aws/ec2/reserved_instances.py,sha256=jv8-VLI5KL8jN1QRI20yim8lzZ7I7wR8a5EF8DckahA,3122
|
|
184
184
|
cartography/intel/aws/ec2/security_groups.py,sha256=vxLeaCpCowkbl-YpON1UdbjtPolMfj_reOEuKujN80Y,6060
|
|
185
|
-
cartography/intel/aws/ec2/snapshots.py,sha256=
|
|
185
|
+
cartography/intel/aws/ec2/snapshots.py,sha256=R3U6ZwE4bQPy5yikLCRcUHyXN1dD7TzS-3jULQO-F0g,5432
|
|
186
186
|
cartography/intel/aws/ec2/subnets.py,sha256=wdv9TXI1BR_iilOCYmYXL2yok8qef49-I77_DPlyheQ,3694
|
|
187
187
|
cartography/intel/aws/ec2/tgw.py,sha256=lTFPlRNoDHNklR38alSywXlSiiTyg86vJNth7Pc4pZQ,9114
|
|
188
188
|
cartography/intel/aws/ec2/util.py,sha256=Pv-x1QEAAmyxcpEl6y8M24ija3ERjXFE36fswuKXHDs,226
|
|
@@ -231,14 +231,16 @@ cartography/intel/gcp/gke.py,sha256=qaTwsVaxkwNhW5_Mw4bedOk7fgJK8y0LwwcYlUABXDg,
|
|
|
231
231
|
cartography/intel/gcp/storage.py,sha256=oO_ayEhkXlj2Gn7T5MU41ZXiqwRwe6Ud4wzqyRTsyf4,9075
|
|
232
232
|
cartography/intel/github/__init__.py,sha256=y876JJGTDJZEOFCDiNCJfcLNxN24pVj4s2N0YmuuoaE,1914
|
|
233
233
|
cartography/intel/github/repos.py,sha256=YPDdBMk6NkZjwPcqPW5LlCy_OS9tKcrZD6ygiUG93J0,21766
|
|
234
|
-
cartography/intel/github/teams.py,sha256=
|
|
234
|
+
cartography/intel/github/teams.py,sha256=mofyJeJVOD7Umh9Rq6QnAwom9bBHBx18kyvFMvQX5YE,5383
|
|
235
235
|
cartography/intel/github/users.py,sha256=kQp0dxzP08DVrdvfVeCciQbrKPbbFvwbR_p_I_XGt7s,3826
|
|
236
|
-
cartography/intel/github/util.py,sha256=
|
|
236
|
+
cartography/intel/github/util.py,sha256=K6hbxypy4luKhIE1Uh5VWZc9OyjMK2OuO00vBAQfloA,8049
|
|
237
237
|
cartography/intel/gsuite/__init__.py,sha256=AGIUskGlLCVGHbnQicNpNWi9AvmV7_7hUKTt-hsB2J8,4306
|
|
238
238
|
cartography/intel/gsuite/api.py,sha256=J0dkNdfBVMrEv8vvStQu7YKVxXSyV45WueFhUS4aOG4,10310
|
|
239
239
|
cartography/intel/jamf/__init__.py,sha256=Nof-LrUeevoieo6oP2GyfTwx8k5TUIgreW6hSj53YjQ,419
|
|
240
240
|
cartography/intel/jamf/computers.py,sha256=EfjlupQ-9HYTjOrmuwrGuJDy9ApAnJvk8WrYcp6_Jkk,1673
|
|
241
241
|
cartography/intel/jamf/util.py,sha256=EAyP8VpOY2uAvW3HtX6r7qORNjGa1Tr3fuqezuLQ0j4,1017
|
|
242
|
+
cartography/intel/kandji/__init__.py,sha256=OHZJNzuNibIfJ51OkL3XL2EdA_ZmvPHPeWCQUld4J64,1079
|
|
243
|
+
cartography/intel/kandji/devices.py,sha256=j_rP6rQ5VPT_XEcGXx7Yt6eCOm1Oe3I2qWIxXODXEcA,2224
|
|
242
244
|
cartography/intel/kubernetes/__init__.py,sha256=jaOTEanWnTrYvcBN1XUC5oqBhz1AJbFmzoT9uu_VBSg,1481
|
|
243
245
|
cartography/intel/kubernetes/namespaces.py,sha256=6o-FgAX_Ai5NCj2xOWM-RNWEvn0gZjVQnZSGCJlcIhw,2710
|
|
244
246
|
cartography/intel/kubernetes/pods.py,sha256=aX3pP_vs6icMe2vK4vgMak6HZ64okhRzoihpkPHscGU,4502
|
|
@@ -319,6 +321,9 @@ cartography/models/duo/user.py,sha256=ih3DH_QveAve4cX9dmIwC5gVN6_RNnuLK3bfJ5I9u6
|
|
|
319
321
|
cartography/models/duo/web_authn_credential.py,sha256=OcZnfG5zCMlphxSltRcAXQ12hHYJjxrBt6A9L28g7Vk,2920
|
|
320
322
|
cartography/models/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
321
323
|
cartography/models/github/teams.py,sha256=mk3OFGTDqWkLz7aX7Q9AtpOMOkZDDGH0MWoVeevK2-k,4376
|
|
324
|
+
cartography/models/kandji/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
325
|
+
cartography/models/kandji/device.py,sha256=C3zPhLi1oPNysbSUr4H2u8b-Xy14sb3FE7YcjCwlntw,2214
|
|
326
|
+
cartography/models/kandji/tenant.py,sha256=KhcbahNBemny3coQPiadIY8B-yDMg_ejYB2BR6vqBfw,674
|
|
322
327
|
cartography/models/lastpass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
323
328
|
cartography/models/lastpass/tenant.py,sha256=TG-9LFo9Sfzb9UgcTt_gFVTKocLItbgQMMPkN_iprXU,618
|
|
324
329
|
cartography/models/lastpass/user.py,sha256=SMTTYN6jgccc9k76hY3rVImElJOhHhZ9f1aZ6JzcrHw,3487
|
|
@@ -326,10 +331,10 @@ cartography/models/semgrep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
326
331
|
cartography/models/semgrep/deployment.py,sha256=or5qZDuR51MXzINpH15jZrqmSUvXQevCNYWJ7D6v-JI,745
|
|
327
332
|
cartography/models/semgrep/findings.py,sha256=xrn8sgXpNMrNJbKQagaAVxaCG9bVjTATSRR2XRBR4rg,5386
|
|
328
333
|
cartography/models/semgrep/locations.py,sha256=kSk7Nn5Mn4Ob84MVZOo2GR0YFi-9Okq9pgA3FfC6_bk,3061
|
|
329
|
-
cartography-0.
|
|
330
|
-
cartography-0.
|
|
331
|
-
cartography-0.
|
|
332
|
-
cartography-0.
|
|
333
|
-
cartography-0.
|
|
334
|
-
cartography-0.
|
|
335
|
-
cartography-0.
|
|
334
|
+
cartography-0.91.0.dist-info/LICENSE,sha256=489ZXeW9G90up6ep-D1n-lJgk9ciNT2yxXpFgRSidtk,11341
|
|
335
|
+
cartography-0.91.0.dist-info/METADATA,sha256=IhgPXxZ4hfyjHAZyMjHdBkM8_s8XmHDgOJMEyOKXgBI,1988
|
|
336
|
+
cartography-0.91.0.dist-info/NOTICE,sha256=YOGAsjFtbyKj5tslYIg6V5jEYRuEvnSsIuDOUKj0Qj4,97
|
|
337
|
+
cartography-0.91.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
338
|
+
cartography-0.91.0.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
|
|
339
|
+
cartography-0.91.0.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
|
|
340
|
+
cartography-0.91.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|