cartography 0.90.0rc2__py3-none-any.whl → 0.92.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/data/indexes.cypher +0 -6
- cartography/intel/aws/ec2/images.py +10 -9
- cartography/intel/aws/ec2/launch_templates.py +110 -78
- cartography/intel/aws/ec2/snapshots.py +4 -2
- cartography/intel/aws/iam.py +1 -0
- 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/intel/okta/__init__.py +1 -1
- cartography/intel/okta/awssaml.py +117 -9
- cartography/models/aws/ec2/launch_template_versions.py +81 -0
- cartography/models/aws/ec2/launch_templates.py +46 -0
- cartography/models/aws/ec2/subnet_instance.py +1 -1
- 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.92.0.dist-info}/METADATA +1 -1
- {cartography-0.90.0rc2.dist-info → cartography-0.92.0.dist-info}/RECORD +27 -21
- {cartography-0.90.0rc2.dist-info → cartography-0.92.0.dist-info}/WHEEL +1 -1
- cartography/data/jobs/cleanup/aws_import_ec2_launch_templates_cleanup.json +0 -13
- {cartography-0.90.0rc2.dist-info → cartography-0.92.0.dist-info}/LICENSE +0 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.92.0.dist-info}/NOTICE +0 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.92.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.90.0rc2.dist-info → cartography-0.92.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
|
cartography/data/indexes.cypher
CHANGED
|
@@ -200,12 +200,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:KMSGrant) ON (n.lastupdated);
|
|
|
200
200
|
CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.id);
|
|
201
201
|
CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.name);
|
|
202
202
|
CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.lastupdated);
|
|
203
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LaunchTemplate) ON (n.id);
|
|
204
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LaunchTemplate) ON (n.name);
|
|
205
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LaunchTemplate) ON (n.lastupdated);
|
|
206
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LaunchTemplateVersion) ON (n.id);
|
|
207
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LaunchTemplateVersion) ON (n.name);
|
|
208
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LaunchTemplateVersion) ON (n.lastupdated);
|
|
209
203
|
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.dnsname);
|
|
210
204
|
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.id);
|
|
211
205
|
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.lastupdated);
|
|
@@ -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
|
|
|
@@ -51,6 +51,7 @@ def get_images(boto3_session: boto3.session.Session, region: str, image_ids: Lis
|
|
|
51
51
|
logger.warning(f"Failed retrieve images for region - {region}. Error - {e}")
|
|
52
52
|
try:
|
|
53
53
|
if image_ids:
|
|
54
|
+
image_ids = [image_id for image_id in image_ids if image_id is not None]
|
|
54
55
|
images_in_use = client.describe_images(ImageIds=image_ids)['Images']
|
|
55
56
|
# Ensure we're not adding duplicates
|
|
56
57
|
_ids = [image["ImageId"] for image in images]
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
3
|
-
from typing import List
|
|
2
|
+
from typing import Any
|
|
4
3
|
|
|
5
4
|
import boto3
|
|
6
5
|
import neo4j
|
|
7
6
|
|
|
8
7
|
from .util import get_botocore_config
|
|
8
|
+
from cartography.client.core.tx import load
|
|
9
|
+
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.models.aws.ec2.launch_template_versions import LaunchTemplateVersionSchema
|
|
11
|
+
from cartography.models.aws.ec2.launch_templates import LaunchTemplateSchema
|
|
9
12
|
from cartography.util import aws_handle_regions
|
|
10
|
-
from cartography.util import run_cleanup_job
|
|
11
13
|
from cartography.util import timeit
|
|
12
14
|
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
@@ -15,101 +17,131 @@ logger = logging.getLogger(__name__)
|
|
|
15
17
|
|
|
16
18
|
@timeit
|
|
17
19
|
@aws_handle_regions
|
|
18
|
-
def get_launch_templates(boto3_session: boto3.session.Session, region: str) ->
|
|
20
|
+
def get_launch_templates(boto3_session: boto3.session.Session, region: str) -> list[dict[str, Any]]:
|
|
19
21
|
client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
|
|
20
22
|
paginator = client.get_paginator('describe_launch_templates')
|
|
21
|
-
templates:
|
|
23
|
+
templates: list[dict[str, Any]] = []
|
|
22
24
|
for page in paginator.paginate():
|
|
23
25
|
templates.extend(page['LaunchTemplates'])
|
|
24
|
-
for template in templates:
|
|
25
|
-
template_versions: List[Dict] = []
|
|
26
|
-
v_paginator = client.get_paginator('describe_launch_template_versions')
|
|
27
|
-
for versions in v_paginator.paginate(LaunchTemplateId=template['LaunchTemplateId']):
|
|
28
|
-
template_versions.extend(versions["LaunchTemplateVersions"])
|
|
29
|
-
template["_template_versions"] = template_versions
|
|
30
26
|
return templates
|
|
31
27
|
|
|
32
28
|
|
|
29
|
+
def transform_launch_templates(templates: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
30
|
+
result: list[dict[str, Any]] = []
|
|
31
|
+
for template in templates:
|
|
32
|
+
current = template.copy()
|
|
33
|
+
current['CreateTime'] = str(int(current['CreateTime'].timestamp()))
|
|
34
|
+
result.append(current)
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
33
38
|
@timeit
|
|
34
39
|
def load_launch_templates(
|
|
35
|
-
neo4j_session: neo4j.Session,
|
|
40
|
+
neo4j_session: neo4j.Session,
|
|
41
|
+
data: list[dict[str, Any]],
|
|
42
|
+
region: str,
|
|
43
|
+
current_aws_account_id: str,
|
|
44
|
+
update_tag: int,
|
|
36
45
|
) -> None:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
template.name = lt.LaunchTemplateName,
|
|
42
|
-
template.create_time = lt.CreateTime,
|
|
43
|
-
template.created_by = lt.CreatedBy
|
|
44
|
-
SET template.default_version_number = lt.DefaultVersionNumber,
|
|
45
|
-
template.latest_version_number = lt.LatestVersionNumber,
|
|
46
|
-
template.lastupdated = $update_tag,
|
|
47
|
-
template.region=$Region
|
|
48
|
-
WITH template, lt._template_versions as versions
|
|
49
|
-
MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
50
|
-
MERGE (aa)-[r:RESOURCE]->(template)
|
|
51
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
52
|
-
SET r.lastupdated = $update_tag
|
|
53
|
-
WITH template, versions
|
|
54
|
-
UNWIND versions as tv
|
|
55
|
-
MERGE (version:LaunchTemplateVersion{id: tv.LaunchTemplateId + '-' + tv.VersionNumber})
|
|
56
|
-
ON CREATE SET version.firstseen = timestamp(),
|
|
57
|
-
version.name = tv.LaunchTemplateName,
|
|
58
|
-
version.create_time = tv.CreateTime,
|
|
59
|
-
version.created_by = tv.CreatedBy,
|
|
60
|
-
version.default_version = tv.DefaultVersion,
|
|
61
|
-
version.version_number = tv.VersionNumber,
|
|
62
|
-
version.version_description = tv.VersionDescription,
|
|
63
|
-
version.kernel_id = tv.LaunchTemplateData.KernelId,
|
|
64
|
-
version.ebs_optimized = tv.LaunchTemplateData.EbsOptimized,
|
|
65
|
-
version.iam_instance_profile_arn = tv.LaunchTemplateData.IamInstanceProfile.Arn,
|
|
66
|
-
version.iam_instance_profile_name = tv.LaunchTemplateData.IamInstanceProfile.Name,
|
|
67
|
-
version.image_id = tv.LaunchTemplateData.ImageId,
|
|
68
|
-
version.instance_type = tv.LaunchTemplateData.InstanceType,
|
|
69
|
-
version.key_name = tv.LaunchTemplateData.KeyName,
|
|
70
|
-
version.monitoring_enabled = tv.LaunchTemplateData.Monitoring.Enabled,
|
|
71
|
-
version.ramdisk_id = tv.LaunchTemplateData.RamdiskId,
|
|
72
|
-
version.disable_api_termination = tv.LaunchTemplateData.DisableApiTermination,
|
|
73
|
-
version.instance_initiated_shutdown_behavior = tv.LaunchTemplateData.InstanceInitiatedShutdownBehavior,
|
|
74
|
-
version.security_group_ids = tv.LaunchTemplateData.SecurityGroupIds,
|
|
75
|
-
version.security_groups = tv.LaunchTemplateData.SecurityGroups
|
|
76
|
-
SET version.lastupdated = $update_tag,
|
|
77
|
-
version.region=$Region
|
|
78
|
-
WITH template, version
|
|
79
|
-
MERGE (template)-[r:VERSION]->(version)
|
|
80
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
81
|
-
SET r.lastupdated = $update_tag
|
|
82
|
-
"""
|
|
83
|
-
for lt in data:
|
|
84
|
-
lt['CreateTime'] = str(int(lt['CreateTime'].timestamp()))
|
|
85
|
-
for tv in lt["_template_versions"]:
|
|
86
|
-
tv['CreateTime'] = str(int(tv['CreateTime'].timestamp()))
|
|
87
|
-
|
|
88
|
-
neo4j_session.run(
|
|
89
|
-
ingest_lt,
|
|
90
|
-
launch_templates=data,
|
|
91
|
-
AWS_ACCOUNT_ID=current_aws_account_id,
|
|
46
|
+
load(
|
|
47
|
+
neo4j_session,
|
|
48
|
+
LaunchTemplateSchema(),
|
|
49
|
+
data,
|
|
92
50
|
Region=region,
|
|
93
|
-
|
|
51
|
+
AWS_ID=current_aws_account_id,
|
|
52
|
+
lastupdated=update_tag,
|
|
94
53
|
)
|
|
95
54
|
|
|
96
55
|
|
|
97
56
|
@timeit
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
57
|
+
@aws_handle_regions
|
|
58
|
+
def get_launch_template_versions(
|
|
59
|
+
boto3_session: boto3.session.Session,
|
|
60
|
+
templates: list[dict[str, Any]],
|
|
61
|
+
region: str,
|
|
62
|
+
) -> list[dict[str, Any]]:
|
|
63
|
+
client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
|
|
64
|
+
v_paginator = client.get_paginator('describe_launch_template_versions')
|
|
65
|
+
template_versions = []
|
|
66
|
+
for template in templates:
|
|
67
|
+
for versions in v_paginator.paginate(LaunchTemplateId=template['LaunchTemplateId']):
|
|
68
|
+
template_versions.extend(versions['LaunchTemplateVersions'])
|
|
69
|
+
return template_versions
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def transform_launch_template_versions(versions: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
73
|
+
result: list[dict[str, Any]] = []
|
|
74
|
+
for version in versions:
|
|
75
|
+
current = version.copy()
|
|
76
|
+
|
|
77
|
+
# Reformat some fields
|
|
78
|
+
current['Id'] = f"{version['LaunchTemplateId']}-{version['VersionNumber']}"
|
|
79
|
+
current['CreateTime'] = str(int(version['CreateTime'].timestamp()))
|
|
80
|
+
|
|
81
|
+
# Handle the nested object returned from boto
|
|
82
|
+
ltd = version['LaunchTemplateData']
|
|
83
|
+
current['KernelId'] = ltd.get('KernelId')
|
|
84
|
+
current['EbsOptimized'] = ltd.get('EbsOptimized')
|
|
85
|
+
current['IamInstanceProfileArn'] = ltd.get('IamInstanceProfileArn')
|
|
86
|
+
current['IamInstanceProfileName'] = ltd.get('IamInstanceProfileName')
|
|
87
|
+
current['ImageId'] = ltd.get('ImageId')
|
|
88
|
+
current['InstanceType'] = ltd.get('InstanceType')
|
|
89
|
+
current['KeyName'] = ltd.get('KeyName')
|
|
90
|
+
current['MonitoringEnabled'] = ltd.get('MonitoringEnabled')
|
|
91
|
+
current['RamdiskId'] = ltd.get('RamdiskId')
|
|
92
|
+
current['DisableApiTermination'] = ltd.get('DisableApiTermination')
|
|
93
|
+
current['InstanceInitiatedShutDownBehavior'] = ltd.get('InstanceInitiatedShutDownBehavior')
|
|
94
|
+
current['SecurityGroupIds'] = ltd.get('SecurityGroupIds')
|
|
95
|
+
current['SecurityGroups'] = ltd.get('SecurityGroups')
|
|
96
|
+
result.append(current)
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@timeit
|
|
101
|
+
def load_launch_template_versions(
|
|
102
|
+
neo4j_session: neo4j.Session,
|
|
103
|
+
data: list[dict[str, Any]],
|
|
104
|
+
region: str,
|
|
105
|
+
current_aws_account_id: str,
|
|
106
|
+
update_tag: int,
|
|
107
|
+
) -> None:
|
|
108
|
+
load(
|
|
101
109
|
neo4j_session,
|
|
102
|
-
|
|
110
|
+
LaunchTemplateVersionSchema(),
|
|
111
|
+
data,
|
|
112
|
+
Region=region,
|
|
113
|
+
AWS_ID=current_aws_account_id,
|
|
114
|
+
lastupdated=update_tag,
|
|
103
115
|
)
|
|
104
116
|
|
|
105
117
|
|
|
118
|
+
@timeit
|
|
119
|
+
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]) -> None:
|
|
120
|
+
logger.info("Running launch template cleanup job.")
|
|
121
|
+
cleanup_job = GraphJob.from_node_schema(LaunchTemplateSchema(), common_job_parameters)
|
|
122
|
+
cleanup_job.run(neo4j_session)
|
|
123
|
+
|
|
124
|
+
cleanup_job = GraphJob.from_node_schema(LaunchTemplateVersionSchema(), common_job_parameters)
|
|
125
|
+
cleanup_job.run(neo4j_session)
|
|
126
|
+
|
|
127
|
+
|
|
106
128
|
@timeit
|
|
107
129
|
def sync_ec2_launch_templates(
|
|
108
|
-
neo4j_session: neo4j.Session,
|
|
109
|
-
|
|
130
|
+
neo4j_session: neo4j.Session,
|
|
131
|
+
boto3_session: boto3.session.Session,
|
|
132
|
+
regions: list[str],
|
|
133
|
+
current_aws_account_id: str,
|
|
134
|
+
update_tag: int,
|
|
135
|
+
common_job_parameters: dict[str, Any],
|
|
110
136
|
) -> None:
|
|
111
137
|
for region in regions:
|
|
112
|
-
logger.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
logger.info(f"Syncing launch templates for region '{region}' in account '{current_aws_account_id}'.")
|
|
139
|
+
templates = get_launch_templates(boto3_session, region)
|
|
140
|
+
templates = transform_launch_templates(templates)
|
|
141
|
+
load_launch_templates(neo4j_session, templates, region, current_aws_account_id, update_tag)
|
|
142
|
+
|
|
143
|
+
versions = get_launch_template_versions(boto3_session, templates, region)
|
|
144
|
+
versions = transform_launch_template_versions(versions)
|
|
145
|
+
load_launch_template_versions(neo4j_session, versions, region, current_aws_account_id, update_tag)
|
|
146
|
+
|
|
147
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -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
|
|
cartography/intel/aws/iam.py
CHANGED
|
@@ -227,6 +227,7 @@ def get_account_access_key_data(boto3_session: boto3.session.Session, username:
|
|
|
227
227
|
logger.warning(
|
|
228
228
|
f"Could not get access key for user {username} due to NoSuchEntityException; skipping.",
|
|
229
229
|
)
|
|
230
|
+
return access_keys
|
|
230
231
|
for access_key in access_keys['AccessKeyMetadata']:
|
|
231
232
|
access_key_id = access_key['AccessKeyId']
|
|
232
233
|
last_used_info = client.get_access_key_last_used(
|
|
@@ -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)
|
|
@@ -68,7 +68,7 @@ def start_okta_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
68
68
|
applications.sync_okta_applications(neo4j_session, config.okta_org_id, config.update_tag, config.okta_api_key)
|
|
69
69
|
factors.sync_users_factors(neo4j_session, config.okta_org_id, config.update_tag, config.okta_api_key, state)
|
|
70
70
|
origins.sync_trusted_origins(neo4j_session, config.okta_org_id, config.update_tag, config.okta_api_key)
|
|
71
|
-
awssaml.sync_okta_aws_saml(neo4j_session, config.okta_saml_role_regex, config.update_tag)
|
|
71
|
+
awssaml.sync_okta_aws_saml(neo4j_session, config.okta_saml_role_regex, config.update_tag, config.okta_org_id)
|
|
72
72
|
|
|
73
73
|
# need creds with permission
|
|
74
74
|
# soft fail as some won't be able to get such high priv token
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
# Okta intel module - AWS SAML
|
|
2
2
|
import logging
|
|
3
3
|
import re
|
|
4
|
+
from collections import namedtuple
|
|
4
5
|
from typing import Dict
|
|
5
6
|
from typing import List
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
8
9
|
import neo4j
|
|
9
10
|
|
|
11
|
+
from cartography.client.core.tx import read_list_of_dicts_tx
|
|
12
|
+
from cartography.client.core.tx import read_single_value_tx
|
|
10
13
|
from cartography.util import timeit
|
|
11
14
|
|
|
12
15
|
|
|
16
|
+
AccountRole = namedtuple('AccountRole', ['account_id', 'role_name'])
|
|
17
|
+
OktaGroup = namedtuple('OktaGroup', ['group_id', 'group_name'])
|
|
18
|
+
GroupRole = namedtuple('GroupRole', ['okta_group_id', 'aws_role_arn'])
|
|
19
|
+
|
|
13
20
|
logger = logging.getLogger(__name__)
|
|
14
21
|
|
|
15
22
|
|
|
@@ -17,17 +24,25 @@ def _parse_regex(regex_string: str) -> str:
|
|
|
17
24
|
return regex_string.replace("{{accountid}}", "P<accountid>").replace("{{role}}", "P<role>").strip()
|
|
18
25
|
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
def _parse_okta_group_name(okta_group_name: str, mapping_regex: str) -> AccountRole | None:
|
|
28
|
+
"""
|
|
29
|
+
Extract AWS account id and AWS role name from the given Okta group name using the given mapping regex.
|
|
30
|
+
"""
|
|
22
31
|
regex = _parse_regex(mapping_regex)
|
|
23
|
-
matches = re.search(regex,
|
|
32
|
+
matches = re.search(regex, okta_group_name)
|
|
24
33
|
if matches:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
account_id = matches.group("accountid")
|
|
35
|
+
role_name = matches.group("role")
|
|
36
|
+
return AccountRole(account_id, role_name)
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def transform_okta_group_to_aws_role(group_id: str, group_name: str, mapping_regex: str) -> Optional[Dict]:
|
|
41
|
+
account_role = _parse_okta_group_name(group_name, mapping_regex)
|
|
42
|
+
if account_role:
|
|
43
|
+
role_arn = f"arn:aws:iam::{account_role.account_id}:role/{account_role.role_name}"
|
|
28
44
|
return {"groupid": group_id, "role": role_arn}
|
|
29
|
-
|
|
30
|
-
return None
|
|
45
|
+
return None
|
|
31
46
|
|
|
32
47
|
|
|
33
48
|
@timeit
|
|
@@ -45,6 +60,7 @@ def query_for_okta_to_aws_role_mapping(neo4j_session: neo4j.Session, mapping_reg
|
|
|
45
60
|
|
|
46
61
|
for res in results:
|
|
47
62
|
has_results = True
|
|
63
|
+
# input: okta group id, okta group name. output: aws role arn.
|
|
48
64
|
mapping = transform_okta_group_to_aws_role(res["group.id"], res["group.name"], mapping_regex)
|
|
49
65
|
if mapping:
|
|
50
66
|
group_to_role_mapping.append(mapping)
|
|
@@ -107,8 +123,96 @@ def _load_human_can_assume_role(neo4j_session: neo4j.Session, okta_update_tag: i
|
|
|
107
123
|
)
|
|
108
124
|
|
|
109
125
|
|
|
126
|
+
def get_awssso_okta_groups(neo4j_session: neo4j.Session, okta_org_id: str) -> list[OktaGroup]:
|
|
127
|
+
"""
|
|
128
|
+
Return list of all Okta group ids in the current Okta organization tied to Okta Applications with name
|
|
129
|
+
"amazon_aws_sso".
|
|
130
|
+
"""
|
|
131
|
+
query = """
|
|
132
|
+
MATCH (g:OktaGroup)-[:APPLICATION]->(a:OktaApplication{name:"amazon_aws_sso"})
|
|
133
|
+
<-[:RESOURCE]-(:OktaOrganization{id: $okta_org_id})
|
|
134
|
+
RETURN g.id as group_id, g.name as group_name
|
|
135
|
+
"""
|
|
136
|
+
result = neo4j_session.read_transaction(read_list_of_dicts_tx, query, okta_org_id=okta_org_id)
|
|
137
|
+
return [OktaGroup(group_name=og['group_name'], group_id=og['group_id']) for og in result]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_awssso_role_arn(account_id: str, role_hint: str, neo4j_session: neo4j.Session) -> str | None:
|
|
141
|
+
"""
|
|
142
|
+
Attempt to return the AWS role ARN for the given AWS account ID and role hint string.
|
|
143
|
+
This function exists to handle that AWS SSO roles have a 'AWSReservedSSO' prefix and a hashed suffix
|
|
144
|
+
Input:
|
|
145
|
+
- account_id: AWS account ID
|
|
146
|
+
- role_hint (str): The `AccountRole.role_name` returned by _parse_okta_group_name(). This is the part of the Okta
|
|
147
|
+
group name that refers to the AWS role name.
|
|
148
|
+
Output:
|
|
149
|
+
- If we are able to find it, returns the matching AWS role ARN.
|
|
150
|
+
"""
|
|
151
|
+
query = """
|
|
152
|
+
MATCH (:AWSAccount{id:$account_id})-[:RESOURCE]->(role:AWSRole{path:"/aws-reserved/sso.amazonaws.com/"})
|
|
153
|
+
WHERE SPLIT(role.name, '_')[1..-1][0] = $role_hint
|
|
154
|
+
RETURN role.arn AS role_arn
|
|
155
|
+
"""
|
|
156
|
+
return neo4j_session.read_transaction(read_single_value_tx, query, account_id=account_id, role_hint=role_hint)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def query_for_okta_to_awssso_role_mapping(
|
|
160
|
+
neo4j_session: neo4j.Session,
|
|
161
|
+
awssso_okta_groups: list[OktaGroup],
|
|
162
|
+
mapping_regex: str,
|
|
163
|
+
) -> list[GroupRole]:
|
|
164
|
+
"""
|
|
165
|
+
Input:
|
|
166
|
+
- neo4j session
|
|
167
|
+
- str list of Okta group names
|
|
168
|
+
- str regex that tells us how to find the AWS role name and account when given an Okta group name
|
|
169
|
+
Output:
|
|
170
|
+
- list of OktaGroup id to AWSRole arn pairs.
|
|
171
|
+
"""
|
|
172
|
+
result = []
|
|
173
|
+
for group in awssso_okta_groups:
|
|
174
|
+
account_role = _parse_okta_group_name(group.group_name, mapping_regex)
|
|
175
|
+
if not account_role:
|
|
176
|
+
logger.info(f"Okta group {group.group_name} has no associated AWS SSO role")
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
role_arn = get_awssso_role_arn(account_role.account_id, account_role.role_name, neo4j_session)
|
|
180
|
+
if role_arn:
|
|
181
|
+
result.append(GroupRole(group.group_id, role_arn))
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _load_awssso_tx(tx: neo4j.Transaction, group_to_role: list[GroupRole], okta_update_tag: int) -> None:
|
|
186
|
+
ingest_statement = """
|
|
187
|
+
UNWIND $GROUP_TO_ROLE as app_data
|
|
188
|
+
MATCH (role:AWSRole{arn: app_data.aws_role_arn})
|
|
189
|
+
MATCH (group:OktaGroup{id: app_data.okta_group_id})
|
|
190
|
+
MERGE (role)<-[r:ALLOWED_BY]-(group)
|
|
191
|
+
ON CREATE SET r.firstseen = timestamp()
|
|
192
|
+
SET r.lastupdated = $okta_update_tag
|
|
193
|
+
"""
|
|
194
|
+
tx.run(
|
|
195
|
+
ingest_statement,
|
|
196
|
+
GROUP_TO_ROLE=[g._asdict() for g in group_to_role],
|
|
197
|
+
okta_update_tag=okta_update_tag,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _load_okta_group_to_awssso_roles(
|
|
202
|
+
neo4j_session: neo4j.Session,
|
|
203
|
+
group_to_role: list[GroupRole],
|
|
204
|
+
okta_update_tag: int,
|
|
205
|
+
) -> None:
|
|
206
|
+
neo4j_session.write_transaction(_load_awssso_tx, group_to_role, okta_update_tag)
|
|
207
|
+
|
|
208
|
+
|
|
110
209
|
@timeit
|
|
111
|
-
def sync_okta_aws_saml(
|
|
210
|
+
def sync_okta_aws_saml(
|
|
211
|
+
neo4j_session: neo4j.Session,
|
|
212
|
+
mapping_regex: str,
|
|
213
|
+
okta_update_tag: int,
|
|
214
|
+
okta_org_id: str,
|
|
215
|
+
) -> None:
|
|
112
216
|
"""
|
|
113
217
|
Sync okta integration with saml. This will link OktaGroups to the AWSRoles they enable.
|
|
114
218
|
This is for people who use the okta saml provider for AWS
|
|
@@ -127,3 +231,7 @@ def sync_okta_aws_saml(neo4j_session: neo4j.Session, mapping_regex: str, okta_up
|
|
|
127
231
|
group_to_role_mapping = query_for_okta_to_aws_role_mapping(neo4j_session, mapping_regex)
|
|
128
232
|
_load_okta_group_to_aws_roles(neo4j_session, group_to_role_mapping, okta_update_tag)
|
|
129
233
|
_load_human_can_assume_role(neo4j_session, okta_update_tag)
|
|
234
|
+
|
|
235
|
+
sso_okta_groups = get_awssso_okta_groups(neo4j_session, okta_org_id)
|
|
236
|
+
group_to_ssorole_mapping = query_for_okta_to_awssso_role_mapping(neo4j_session, sso_okta_groups, mapping_regex)
|
|
237
|
+
_load_okta_group_to_awssso_roles(neo4j_session, group_to_ssorole_mapping, okta_update_tag)
|
|
@@ -0,0 +1,81 @@
|
|
|
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 LaunchTemplateVersionNodeProperties(CartographyNodeProperties):
|
|
16
|
+
id: PropertyRef = PropertyRef('Id')
|
|
17
|
+
name: PropertyRef = PropertyRef('LaunchTemplateName')
|
|
18
|
+
create_time: PropertyRef = PropertyRef('CreateTime')
|
|
19
|
+
created_by: PropertyRef = PropertyRef('CreatedBy')
|
|
20
|
+
default_version: PropertyRef = PropertyRef('DefaultVersion')
|
|
21
|
+
version_number: PropertyRef = PropertyRef('VersionNumber')
|
|
22
|
+
version_description: PropertyRef = PropertyRef('VersionDescription')
|
|
23
|
+
kernel_id: PropertyRef = PropertyRef('KernelId')
|
|
24
|
+
ebs_optimized: PropertyRef = PropertyRef('EbsOptimized')
|
|
25
|
+
iam_instance_profile_arn: PropertyRef = PropertyRef('IamInstanceProfileArn')
|
|
26
|
+
iam_instance_profile_name: PropertyRef = PropertyRef('IamInstanceProfileName')
|
|
27
|
+
image_id: PropertyRef = PropertyRef('ImageId')
|
|
28
|
+
instance_type: PropertyRef = PropertyRef('InstanceType')
|
|
29
|
+
key_name: PropertyRef = PropertyRef('KeyName')
|
|
30
|
+
monitoring_enabled: PropertyRef = PropertyRef('MonitoringEnabled')
|
|
31
|
+
ramdisk_id: PropertyRef = PropertyRef('RamdiskId')
|
|
32
|
+
disable_api_termination: PropertyRef = PropertyRef('DisableApiTermination')
|
|
33
|
+
instance_initiated_shutdown_behavior: PropertyRef = PropertyRef('InstanceInitiatedShutdownBehavior')
|
|
34
|
+
security_group_ids: PropertyRef = PropertyRef('SecurityGroupIds')
|
|
35
|
+
security_groups: PropertyRef = PropertyRef('SecurityGroups')
|
|
36
|
+
region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
|
|
37
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class LaunchTemplateVersionToAwsAccountRelProperties(CartographyRelProperties):
|
|
42
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class LaunchTemplateVersionToAWSAccount(CartographyRelSchema):
|
|
47
|
+
target_node_label: str = 'AWSAccount'
|
|
48
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
49
|
+
{'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
|
|
50
|
+
)
|
|
51
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
52
|
+
rel_label: str = "RESOURCE"
|
|
53
|
+
properties: LaunchTemplateVersionToAwsAccountRelProperties = LaunchTemplateVersionToAwsAccountRelProperties()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class LaunchTemplateVersionToLTRelProperties(CartographyRelProperties):
|
|
58
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class LaunchTemplateVersionToLT(CartographyRelSchema):
|
|
63
|
+
target_node_label: str = 'LaunchTemplate'
|
|
64
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
65
|
+
{'id': PropertyRef('LaunchTemplateId')},
|
|
66
|
+
)
|
|
67
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
68
|
+
rel_label: str = "VERSION"
|
|
69
|
+
properties: LaunchTemplateVersionToLTRelProperties = LaunchTemplateVersionToLTRelProperties()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class LaunchTemplateVersionSchema(CartographyNodeSchema):
|
|
74
|
+
label: str = 'LaunchTemplateVersion'
|
|
75
|
+
properties: LaunchTemplateVersionNodeProperties = LaunchTemplateVersionNodeProperties()
|
|
76
|
+
sub_resource_relationship: LaunchTemplateVersionToAWSAccount = LaunchTemplateVersionToAWSAccount()
|
|
77
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
78
|
+
[
|
|
79
|
+
LaunchTemplateVersionToLT(),
|
|
80
|
+
],
|
|
81
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
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 LaunchTemplateNodeProperties(CartographyNodeProperties):
|
|
15
|
+
id: PropertyRef = PropertyRef('LaunchTemplateId')
|
|
16
|
+
launch_template_id: PropertyRef = PropertyRef('LaunchTemplateId')
|
|
17
|
+
name: PropertyRef = PropertyRef('LaunchTemplateName')
|
|
18
|
+
create_time: PropertyRef = PropertyRef('CreateTime')
|
|
19
|
+
created_by: PropertyRef = PropertyRef('CreatedBy')
|
|
20
|
+
default_version_number: PropertyRef = PropertyRef('DefaultVersionNumber')
|
|
21
|
+
latest_version_number: PropertyRef = PropertyRef('LatestVersionNumber')
|
|
22
|
+
region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
|
|
23
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class LaunchTemplateToAwsAccountRelProperties(CartographyRelProperties):
|
|
28
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class LaunchTemplateToAWSAccount(CartographyRelSchema):
|
|
33
|
+
target_node_label: str = 'AWSAccount'
|
|
34
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
35
|
+
{'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
|
|
36
|
+
)
|
|
37
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
38
|
+
rel_label: str = "RESOURCE"
|
|
39
|
+
properties: LaunchTemplateToAwsAccountRelProperties = LaunchTemplateToAwsAccountRelProperties()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class LaunchTemplateSchema(CartographyNodeSchema):
|
|
44
|
+
label: str = 'LaunchTemplate'
|
|
45
|
+
properties: LaunchTemplateNodeProperties = LaunchTemplateNodeProperties()
|
|
46
|
+
sub_resource_relationship: LaunchTemplateToAWSAccount = LaunchTemplateToAWSAccount()
|
|
@@ -15,7 +15,7 @@ from cartography.models.core.relationships import TargetNodeMatcher
|
|
|
15
15
|
class EC2SubnetInstanceNodeProperties(CartographyNodeProperties):
|
|
16
16
|
# arn: PropertyRef = PropertyRef('Arn', extra_index=True) TODO use arn; issue #1024
|
|
17
17
|
id: PropertyRef = PropertyRef('SubnetId')
|
|
18
|
-
|
|
18
|
+
subnetid: PropertyRef = PropertyRef('SubnetId', extra_index=True)
|
|
19
19
|
region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
|
|
20
20
|
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
21
21
|
|
|
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
|
|
@@ -12,7 +12,7 @@ cartography/client/aws/iam.py,sha256=dYsGikc36DEsSeR2XVOVFFUDwuU9yWj_EVkpgVYCFgM
|
|
|
12
12
|
cartography/client/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
cartography/client/core/tx.py,sha256=4_kTBxrtlwsOM-e8Xtjf7wmmzwZ-DGRJL0rPFp0Xj0Q,10805
|
|
14
14
|
cartography/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
cartography/data/indexes.cypher,sha256=
|
|
15
|
+
cartography/data/indexes.cypher,sha256=PTQEUbC_Kmjj_wM-j6NJqLvETRIORreeqF6WlKmnHKg,27395
|
|
16
16
|
cartography/data/permission_relationships.yaml,sha256=RuKGGc_3ZUQ7ag0MssB8k_zaonCkVM5E8I_svBWTmGc,969
|
|
17
17
|
cartography/data/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
cartography/data/jobs/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,7 +36,6 @@ cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json,sha256=
|
|
|
36
36
|
cartography/data/jobs/cleanup/aws_import_apigateway_cleanup.json,sha256=wCV95ydo3dmlhK7VrDrxCqrP6dbhCCMTzcz_qaJQ4Jo,2189
|
|
37
37
|
cartography/data/jobs/cleanup/aws_import_config_cleanup.json,sha256=VCAJjEnFcQUR16VxKdpsOkBJEnhMuLk-7Kgm_p9k1NM,754
|
|
38
38
|
cartography/data/jobs/cleanup/aws_import_ec2_launch_configurations_cleanup.json,sha256=x9IIzb9Sr1353ygkA-qqUUbZS9XO2v3GzUHe-0J4Pw8,281
|
|
39
|
-
cartography/data/jobs/cleanup/aws_import_ec2_launch_templates_cleanup.json,sha256=tuJzb1wmKSPUiShX8zgNTz7Il_XhXZ7_uQE4_6iUjFI,524
|
|
40
39
|
cartography/data/jobs/cleanup/aws_import_ec2_security_groupinfo_cleanup.json,sha256=CackEgSs1PN15pTg8oIdS0amB-n-PsKODLAaqC3gf_A,1183
|
|
41
40
|
cartography/data/jobs/cleanup/aws_import_ecr_cleanup.json,sha256=7Sga9WlbhHe-VyoFaF0LrlhbAFvSSOjVKiRf_VW8To8,1355
|
|
42
41
|
cartography/data/jobs/cleanup/aws_import_ecs_cleanup.json,sha256=6HtmZy7gNC0ZxLU7I6C2KKcqpZhYRFyaJZCDA50DzAs,2126
|
|
@@ -153,7 +152,7 @@ cartography/intel/aws/eks.py,sha256=OerAX7qT2uGPbqliPvuy8JZUIgle_KMlnkkHxk8O5fk,
|
|
|
153
152
|
cartography/intel/aws/elasticache.py,sha256=fCI47aDFmIDyE26GiReKYb6XIZUwrzcvsXBQ4ruFhuI,4427
|
|
154
153
|
cartography/intel/aws/elasticsearch.py,sha256=ZL7MkXF_bXRSoXuDSI1dwGckRLG2zDB8LuAD07vSLnE,8374
|
|
155
154
|
cartography/intel/aws/emr.py,sha256=xhWBVZngxJRFjMEDxwq3G6SgytRGLq0v2a_CeDvByR0,3372
|
|
156
|
-
cartography/intel/aws/iam.py,sha256=
|
|
155
|
+
cartography/intel/aws/iam.py,sha256=eLw0NkBGKzCI_tQ3wmrx3aUibQerrsxKJd3d0RCKcKQ,32374
|
|
157
156
|
cartography/intel/aws/inspector.py,sha256=6enCu2USefuGT3FqA0Vto6i-z4BrL2HC_clbiXSLIlo,8654
|
|
158
157
|
cartography/intel/aws/kms.py,sha256=bZUzMxAH_DsAcGTJBs08gg2tLKYu-QWjvMvV9C-6v50,11731
|
|
159
158
|
cartography/intel/aws/lambda_function.py,sha256=KKTyn53xpaMI9WvIqxmsOASFwflHt-2_5ow-zUFc2wg,9890
|
|
@@ -172,17 +171,17 @@ cartography/intel/aws/ssm.py,sha256=IDOYa8v2FgziU8nBOZ7wyDG4o_nFYshbB-si9Ut_9Zc,
|
|
|
172
171
|
cartography/intel/aws/ec2/__init__.py,sha256=IDK2Yap7mllK_ab6yVMLXatJ94znIkn-szv5RJP5fbo,346
|
|
173
172
|
cartography/intel/aws/ec2/auto_scaling_groups.py,sha256=4erjP31KSVW-Pp2ASmDox_VLp_AQUAin4KYxfZKZcSM,9223
|
|
174
173
|
cartography/intel/aws/ec2/elastic_ip_addresses.py,sha256=0k4NwS73VyWbEj5jXvSkaq2RNvmAlBlrN-UKa_Bj0uk,3957
|
|
175
|
-
cartography/intel/aws/ec2/images.py,sha256=
|
|
174
|
+
cartography/intel/aws/ec2/images.py,sha256=heElwHJGqVD3iUJjxwA_Sdc3CmE4HPs00CTMHuQ1wkc,3782
|
|
176
175
|
cartography/intel/aws/ec2/instances.py,sha256=mnTjdBY-4D-TGhH29UrSaLUW0Uft0JApDIJkkLz4zPc,12170
|
|
177
176
|
cartography/intel/aws/ec2/internet_gateways.py,sha256=dI-4-85_3DGGZZBcY_DN6XqESx9P26S6jKok314lcnQ,2883
|
|
178
177
|
cartography/intel/aws/ec2/key_pairs.py,sha256=SvRgd56vE4eouvTSNoFK8PP8HYoECO91goxc36oq_FY,2508
|
|
179
|
-
cartography/intel/aws/ec2/launch_templates.py,sha256=
|
|
178
|
+
cartography/intel/aws/ec2/launch_templates.py,sha256=YhLh_O2cZbeoKXA6MXmwkxJJi0ubZb5FyouTYykuq1k,5372
|
|
180
179
|
cartography/intel/aws/ec2/load_balancer_v2s.py,sha256=95FfQQn740gexINIHDJizOM4OKzRtQT_y2XQMipQ5Dg,8661
|
|
181
180
|
cartography/intel/aws/ec2/load_balancers.py,sha256=1GwErzGqi3BKCARqfGJcD_r_D84rFKVy5kNMas9jAok,6756
|
|
182
181
|
cartography/intel/aws/ec2/network_interfaces.py,sha256=CzF8PooCYUQ2pk8DR8JDAhkWRUQSBj_27OsIfkL_-Cs,9199
|
|
183
182
|
cartography/intel/aws/ec2/reserved_instances.py,sha256=jv8-VLI5KL8jN1QRI20yim8lzZ7I7wR8a5EF8DckahA,3122
|
|
184
183
|
cartography/intel/aws/ec2/security_groups.py,sha256=vxLeaCpCowkbl-YpON1UdbjtPolMfj_reOEuKujN80Y,6060
|
|
185
|
-
cartography/intel/aws/ec2/snapshots.py,sha256=
|
|
184
|
+
cartography/intel/aws/ec2/snapshots.py,sha256=R3U6ZwE4bQPy5yikLCRcUHyXN1dD7TzS-3jULQO-F0g,5432
|
|
186
185
|
cartography/intel/aws/ec2/subnets.py,sha256=wdv9TXI1BR_iilOCYmYXL2yok8qef49-I77_DPlyheQ,3694
|
|
187
186
|
cartography/intel/aws/ec2/tgw.py,sha256=lTFPlRNoDHNklR38alSywXlSiiTyg86vJNth7Pc4pZQ,9114
|
|
188
187
|
cartography/intel/aws/ec2/util.py,sha256=Pv-x1QEAAmyxcpEl6y8M24ija3ERjXFE36fswuKXHDs,226
|
|
@@ -231,14 +230,16 @@ cartography/intel/gcp/gke.py,sha256=qaTwsVaxkwNhW5_Mw4bedOk7fgJK8y0LwwcYlUABXDg,
|
|
|
231
230
|
cartography/intel/gcp/storage.py,sha256=oO_ayEhkXlj2Gn7T5MU41ZXiqwRwe6Ud4wzqyRTsyf4,9075
|
|
232
231
|
cartography/intel/github/__init__.py,sha256=y876JJGTDJZEOFCDiNCJfcLNxN24pVj4s2N0YmuuoaE,1914
|
|
233
232
|
cartography/intel/github/repos.py,sha256=YPDdBMk6NkZjwPcqPW5LlCy_OS9tKcrZD6ygiUG93J0,21766
|
|
234
|
-
cartography/intel/github/teams.py,sha256=
|
|
233
|
+
cartography/intel/github/teams.py,sha256=mofyJeJVOD7Umh9Rq6QnAwom9bBHBx18kyvFMvQX5YE,5383
|
|
235
234
|
cartography/intel/github/users.py,sha256=kQp0dxzP08DVrdvfVeCciQbrKPbbFvwbR_p_I_XGt7s,3826
|
|
236
|
-
cartography/intel/github/util.py,sha256=
|
|
235
|
+
cartography/intel/github/util.py,sha256=K6hbxypy4luKhIE1Uh5VWZc9OyjMK2OuO00vBAQfloA,8049
|
|
237
236
|
cartography/intel/gsuite/__init__.py,sha256=AGIUskGlLCVGHbnQicNpNWi9AvmV7_7hUKTt-hsB2J8,4306
|
|
238
237
|
cartography/intel/gsuite/api.py,sha256=J0dkNdfBVMrEv8vvStQu7YKVxXSyV45WueFhUS4aOG4,10310
|
|
239
238
|
cartography/intel/jamf/__init__.py,sha256=Nof-LrUeevoieo6oP2GyfTwx8k5TUIgreW6hSj53YjQ,419
|
|
240
239
|
cartography/intel/jamf/computers.py,sha256=EfjlupQ-9HYTjOrmuwrGuJDy9ApAnJvk8WrYcp6_Jkk,1673
|
|
241
240
|
cartography/intel/jamf/util.py,sha256=EAyP8VpOY2uAvW3HtX6r7qORNjGa1Tr3fuqezuLQ0j4,1017
|
|
241
|
+
cartography/intel/kandji/__init__.py,sha256=OHZJNzuNibIfJ51OkL3XL2EdA_ZmvPHPeWCQUld4J64,1079
|
|
242
|
+
cartography/intel/kandji/devices.py,sha256=j_rP6rQ5VPT_XEcGXx7Yt6eCOm1Oe3I2qWIxXODXEcA,2224
|
|
242
243
|
cartography/intel/kubernetes/__init__.py,sha256=jaOTEanWnTrYvcBN1XUC5oqBhz1AJbFmzoT9uu_VBSg,1481
|
|
243
244
|
cartography/intel/kubernetes/namespaces.py,sha256=6o-FgAX_Ai5NCj2xOWM-RNWEvn0gZjVQnZSGCJlcIhw,2710
|
|
244
245
|
cartography/intel/kubernetes/pods.py,sha256=aX3pP_vs6icMe2vK4vgMak6HZ64okhRzoihpkPHscGU,4502
|
|
@@ -251,9 +252,9 @@ cartography/intel/oci/__init__.py,sha256=AZmRX6EO4LUnynDtIKHxtZ_Ab2-CYPPc2u5d0Q2
|
|
|
251
252
|
cartography/intel/oci/iam.py,sha256=zPrJeoMoO3ZkjBfWbTttjrcUvxxMuWquLTmsDH5MgOI,17712
|
|
252
253
|
cartography/intel/oci/organizations.py,sha256=tzQkZfE4LPoS-6lXBRQGyhq8aJLZUJ1_q75Q9eTBke0,4086
|
|
253
254
|
cartography/intel/oci/utils.py,sha256=UbX9jib4sWEdKeAt2CeCo4k9shUiWY08oTfQz_nDvjA,3223
|
|
254
|
-
cartography/intel/okta/__init__.py,sha256=
|
|
255
|
+
cartography/intel/okta/__init__.py,sha256=i5YY9mIDQ2-IBnCSWf4rToYMa9fQQIxucCnl0TXK2Uc,3833
|
|
255
256
|
cartography/intel/okta/applications.py,sha256=ZqUn-bru6Kh75vpUeRnMurUBh0rGBRpI2b2V09HOOQw,12866
|
|
256
|
-
cartography/intel/okta/awssaml.py,sha256=
|
|
257
|
+
cartography/intel/okta/awssaml.py,sha256=Rw0mrJ7NY5xjfEO_ijMqi1VEbr0FSasfrvGtoCPy1aU,9136
|
|
257
258
|
cartography/intel/okta/factors.py,sha256=1bLnF4MRf0MYzzhT2tfM4jdfkjE1bkQn6_WuOqED2K4,4955
|
|
258
259
|
cartography/intel/okta/groups.py,sha256=GxaixbY5KWkalj2rY6nWwe_IskVVowOAPo88OZIGcPY,10172
|
|
259
260
|
cartography/intel/okta/organization.py,sha256=YLQc7ETdtf8Vc-CRCYivV_xmVl2Oz0Px53anJHYp-p8,821
|
|
@@ -281,6 +282,8 @@ cartography/models/aws/ec2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
281
282
|
cartography/models/aws/ec2/images.py,sha256=uGhXS7Xb6sKUdwwkS0O0dWP4sIREjusUaV_unODv9gE,3012
|
|
282
283
|
cartography/models/aws/ec2/instances.py,sha256=cNMHngdGNRhxoyID6AmG2F7CQGC1fYani8DV8lSKvsI,3902
|
|
283
284
|
cartography/models/aws/ec2/keypairs.py,sha256=scKC3SdExHAWkPNmb6tT9LK-9q4sweqS2ejFzMec10M,2630
|
|
285
|
+
cartography/models/aws/ec2/launch_template_versions.py,sha256=RitfnAuAj0XpFsCXkRbtUhHMAi8Vsvmtury231eKvGU,3897
|
|
286
|
+
cartography/models/aws/ec2/launch_templates.py,sha256=GqiwFuMp72LNSt2eQlp2WfdU_vHsom-xKV5AaUewSHQ,2157
|
|
284
287
|
cartography/models/aws/ec2/loadbalancerv2.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
285
288
|
cartography/models/aws/ec2/networkinterface_instance.py,sha256=t3oqcQ4GjYf7dwqPUGCiXd70ie4ibYLilOXiE5_Ad8g,4707
|
|
286
289
|
cartography/models/aws/ec2/networkinterfaces.py,sha256=z1-Dl6I79-TCxXKG8QBpSKga93lPCPaLR1XqKJZK3ME,4127
|
|
@@ -288,7 +291,7 @@ cartography/models/aws/ec2/privateip_networkinterface.py,sha256=j8MyiZsiUCuzuGUH
|
|
|
288
291
|
cartography/models/aws/ec2/reservations.py,sha256=dE9uSB3Em-ca1dDbetbu79JXr4ZWHC3r5gA1S3mjhVU,1930
|
|
289
292
|
cartography/models/aws/ec2/securitygroup_instance.py,sha256=RZS9TzHHatTOESQgcs5_YHmF9sM7pRkUq2VPjk9FJlU,2876
|
|
290
293
|
cartography/models/aws/ec2/securitygroup_networkinterface.py,sha256=PiaA8J82kybZyZ1wDsa-ACIDa88vt4NoA3smGNiwl14,2399
|
|
291
|
-
cartography/models/aws/ec2/subnet_instance.py,sha256=
|
|
294
|
+
cartography/models/aws/ec2/subnet_instance.py,sha256=ct_ibXiPN2C5ld06TczwSTXtsnlov5VCW6elphtbvPs,2752
|
|
292
295
|
cartography/models/aws/ec2/subnet_networkinterface.py,sha256=JHlxfBojBw7LfJS4a5LpVGM28MUu451PUrrwbbOPGuQ,3614
|
|
293
296
|
cartography/models/aws/ec2/volumes.py,sha256=WSP7YNZeJE3s4wnY9QrIAbcJN3OathqNgEBX0cVahDg,4470
|
|
294
297
|
cartography/models/aws/eks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -319,6 +322,9 @@ cartography/models/duo/user.py,sha256=ih3DH_QveAve4cX9dmIwC5gVN6_RNnuLK3bfJ5I9u6
|
|
|
319
322
|
cartography/models/duo/web_authn_credential.py,sha256=OcZnfG5zCMlphxSltRcAXQ12hHYJjxrBt6A9L28g7Vk,2920
|
|
320
323
|
cartography/models/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
321
324
|
cartography/models/github/teams.py,sha256=mk3OFGTDqWkLz7aX7Q9AtpOMOkZDDGH0MWoVeevK2-k,4376
|
|
325
|
+
cartography/models/kandji/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
326
|
+
cartography/models/kandji/device.py,sha256=C3zPhLi1oPNysbSUr4H2u8b-Xy14sb3FE7YcjCwlntw,2214
|
|
327
|
+
cartography/models/kandji/tenant.py,sha256=KhcbahNBemny3coQPiadIY8B-yDMg_ejYB2BR6vqBfw,674
|
|
322
328
|
cartography/models/lastpass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
323
329
|
cartography/models/lastpass/tenant.py,sha256=TG-9LFo9Sfzb9UgcTt_gFVTKocLItbgQMMPkN_iprXU,618
|
|
324
330
|
cartography/models/lastpass/user.py,sha256=SMTTYN6jgccc9k76hY3rVImElJOhHhZ9f1aZ6JzcrHw,3487
|
|
@@ -326,10 +332,10 @@ cartography/models/semgrep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
326
332
|
cartography/models/semgrep/deployment.py,sha256=or5qZDuR51MXzINpH15jZrqmSUvXQevCNYWJ7D6v-JI,745
|
|
327
333
|
cartography/models/semgrep/findings.py,sha256=xrn8sgXpNMrNJbKQagaAVxaCG9bVjTATSRR2XRBR4rg,5386
|
|
328
334
|
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.
|
|
335
|
+
cartography-0.92.0.dist-info/LICENSE,sha256=489ZXeW9G90up6ep-D1n-lJgk9ciNT2yxXpFgRSidtk,11341
|
|
336
|
+
cartography-0.92.0.dist-info/METADATA,sha256=HXdDXVgGGUnCSRNCb9yiLfbWFxQ25iXo8YJGnKk0NvI,1988
|
|
337
|
+
cartography-0.92.0.dist-info/NOTICE,sha256=YOGAsjFtbyKj5tslYIg6V5jEYRuEvnSsIuDOUKj0Qj4,97
|
|
338
|
+
cartography-0.92.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
339
|
+
cartography-0.92.0.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
|
|
340
|
+
cartography-0.92.0.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
|
|
341
|
+
cartography-0.92.0.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"statements": [{
|
|
3
|
-
"query": "MATCH (n:LaunchTemplateVersion)<-[:VERSION]-(:LaunchTemplate)<-[:RESOURCE]-(:AWSAccount{id: $AWS_ID}) WHERE n.lastupdated <> $UPDATE_TAG WITH n LIMIT $LIMIT_SIZE DETACH DELETE (n)",
|
|
4
|
-
"iterative": true,
|
|
5
|
-
"iterationsize": 100
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"query": "MATCH (n:LaunchTemplate)<-[:RESOURCE]-(:AWSAccount{id: $AWS_ID}) WHERE n.lastupdated <> $UPDATE_TAG WITH n LIMIT $LIMIT_SIZE DETACH DELETE (n)",
|
|
9
|
-
"iterative": true,
|
|
10
|
-
"iterationsize": 100
|
|
11
|
-
}],
|
|
12
|
-
"name": "cleanup LaunchTemplate"
|
|
13
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|