cartography 0.101.0rc2__py3-none-any.whl → 0.101.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cartography might be problematic. Click here for more details.
- cartography/_version.py +2 -2
- cartography/data/indexes.cypher +0 -6
- cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json +0 -5
- cartography/intel/aws/ec2/launch_templates.py +24 -7
- cartography/intel/aws/ec2/load_balancers.py +126 -148
- cartography/intel/crowdstrike/__init__.py +17 -5
- cartography/intel/crowdstrike/endpoints.py +12 -44
- cartography/intel/gcp/__init__.py +7 -2
- cartography/intel/gsuite/__init__.py +8 -0
- cartography/intel/kandji/devices.py +27 -3
- cartography/models/aws/ec2/load_balancer_listeners.py +68 -0
- cartography/models/aws/ec2/load_balancers.py +102 -0
- cartography/models/crowdstrike/__init__.py +0 -0
- cartography/models/crowdstrike/hosts.py +49 -0
- cartography/stats.py +1 -1
- {cartography-0.101.0rc2.dist-info → cartography-0.101.1.dist-info}/METADATA +4 -3
- {cartography-0.101.0rc2.dist-info → cartography-0.101.1.dist-info}/RECORD +21 -17
- {cartography-0.101.0rc2.dist-info → cartography-0.101.1.dist-info}/WHEEL +0 -0
- {cartography-0.101.0rc2.dist-info → cartography-0.101.1.dist-info}/entry_points.txt +0 -0
- {cartography-0.101.0rc2.dist-info → cartography-0.101.1.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.101.0rc2.dist-info → cartography-0.101.1.dist-info}/top_level.txt +0 -0
cartography/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.101.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 101,
|
|
20
|
+
__version__ = version = '0.101.1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 101, 1)
|
cartography/data/indexes.cypher
CHANGED
|
@@ -65,9 +65,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:AccountAccessKey) ON (n.accesskeyid);
|
|
|
65
65
|
CREATE INDEX IF NOT EXISTS FOR (n:AccountAccessKey) ON (n.lastupdated);
|
|
66
66
|
CREATE INDEX IF NOT EXISTS FOR (n:AutoScalingGroup) ON (n.arn);
|
|
67
67
|
CREATE INDEX IF NOT EXISTS FOR (n:AutoScalingGroup) ON (n.lastupdated);
|
|
68
|
-
CREATE INDEX IF NOT EXISTS FOR (n:CrowdstrikeHost) ON (n.id);
|
|
69
|
-
CREATE INDEX IF NOT EXISTS FOR (n:CrowdstrikeHost) ON (n.instance_id);
|
|
70
|
-
CREATE INDEX IF NOT EXISTS FOR (n:CrowdstrikeHost) ON (n.lastupdated);
|
|
71
68
|
CREATE INDEX IF NOT EXISTS FOR (n:CVE) ON (n.id);
|
|
72
69
|
CREATE INDEX IF NOT EXISTS FOR (n:CVE) ON (n.lastupdated);
|
|
73
70
|
CREATE INDEX IF NOT EXISTS FOR (n:Dependency) ON (n.id);
|
|
@@ -194,9 +191,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:KMSGrant) ON (n.lastupdated);
|
|
|
194
191
|
CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.id);
|
|
195
192
|
CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.name);
|
|
196
193
|
CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.lastupdated);
|
|
197
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.dnsname);
|
|
198
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.id);
|
|
199
|
-
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.lastupdated);
|
|
200
194
|
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancerV2) ON (n.dnsname);
|
|
201
195
|
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancerV2) ON (n.id);
|
|
202
196
|
CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancerV2) ON (n.lastupdated);
|
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
"iterative": true,
|
|
6
6
|
"iterationsize": 100
|
|
7
7
|
},
|
|
8
|
-
{
|
|
9
|
-
"query": "MATCH (h:CrowdstrikeHost) WHERE h.lastupdated <> $UPDATE_TAG WITH h LIMIT $LIMIT_SIZE DETACH DELETE (h)",
|
|
10
|
-
"iterative": true,
|
|
11
|
-
"iterationsize": 100
|
|
12
|
-
},
|
|
13
8
|
{
|
|
14
9
|
"query": "MATCH (:CrowdstrikeFinding)<-[hc:HAS_CVE]-(:SpotlightVulnerability) WHERE hc.lastupdated <> $UPDATE_TAG WITH hc LIMIT $LIMIT_SIZE DELETE (hc)",
|
|
15
10
|
"iterative": true,
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
4
|
import boto3
|
|
5
|
+
import botocore
|
|
5
6
|
import neo4j
|
|
6
7
|
|
|
7
8
|
from .util import get_botocore_config
|
|
@@ -56,15 +57,27 @@ def get_launch_template_versions_by_template(
|
|
|
56
57
|
client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
|
|
57
58
|
v_paginator = client.get_paginator('describe_launch_template_versions')
|
|
58
59
|
template_versions = []
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
try:
|
|
61
|
+
for versions in v_paginator.paginate(LaunchTemplateId=launch_template_id):
|
|
62
|
+
template_versions.extend(versions['LaunchTemplateVersions'])
|
|
63
|
+
except botocore.exceptions.ClientError as e:
|
|
64
|
+
error_code = e.response['Error']['Code']
|
|
65
|
+
if error_code == 'InvalidLaunchTemplateId.NotFound':
|
|
66
|
+
logger.warning("Launch template %s no longer exists in region %s", launch_template_id, region)
|
|
67
|
+
else:
|
|
68
|
+
raise
|
|
61
69
|
return template_versions
|
|
62
70
|
|
|
63
71
|
|
|
64
|
-
def transform_launch_templates(templates: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
72
|
+
def transform_launch_templates(templates: list[dict[str, Any]], versions: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
73
|
+
valid_template_ids = {v['LaunchTemplateId'] for v in versions}
|
|
65
74
|
result: list[dict[str, Any]] = []
|
|
66
75
|
for template in templates:
|
|
76
|
+
if template['LaunchTemplateId'] not in valid_template_ids:
|
|
77
|
+
continue
|
|
78
|
+
|
|
67
79
|
current = template.copy()
|
|
80
|
+
# Convert CreateTime to timestamp string
|
|
68
81
|
current['CreateTime'] = str(int(current['CreateTime'].timestamp()))
|
|
69
82
|
result.append(current)
|
|
70
83
|
return result
|
|
@@ -157,9 +170,13 @@ def sync_ec2_launch_templates(
|
|
|
157
170
|
logger.info(f"Syncing launch templates for region '{region}' in account '{current_aws_account_id}'.")
|
|
158
171
|
templates = get_launch_templates(boto3_session, region)
|
|
159
172
|
versions = get_launch_template_versions(boto3_session, region, templates)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
|
|
174
|
+
# Transform and load the templates that have versions
|
|
175
|
+
transformed_templates = transform_launch_templates(templates, versions)
|
|
176
|
+
load_launch_templates(neo4j_session, transformed_templates, region, current_aws_account_id, update_tag)
|
|
177
|
+
|
|
178
|
+
# Transform and load the versions
|
|
179
|
+
transformed_versions = transform_launch_template_versions(versions)
|
|
180
|
+
load_launch_template_versions(neo4j_session, transformed_versions, region, current_aws_account_id, update_tag)
|
|
164
181
|
|
|
165
182
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -1,190 +1,168 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Dict
|
|
3
|
-
from typing import List
|
|
4
2
|
|
|
5
3
|
import boto3
|
|
6
4
|
import neo4j
|
|
7
5
|
|
|
8
6
|
from .util import get_botocore_config
|
|
7
|
+
from cartography.client.core.tx import load
|
|
8
|
+
from cartography.graph.job import GraphJob
|
|
9
|
+
from cartography.models.aws.ec2.load_balancer_listeners import ELBListenerSchema
|
|
10
|
+
from cartography.models.aws.ec2.load_balancers import LoadBalancerSchema
|
|
9
11
|
from cartography.util import aws_handle_regions
|
|
10
|
-
from cartography.util import run_cleanup_job
|
|
11
12
|
from cartography.util import timeit
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
def _get_listener_id(load_balancer_id: str, port: int, protocol: str) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Generate a unique ID for a load balancer listener.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
load_balancer_id: The ID of the load balancer
|
|
23
|
+
port: The listener port
|
|
24
|
+
protocol: The listener protocol
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A unique ID string for the listener
|
|
28
|
+
"""
|
|
29
|
+
return f"{load_balancer_id}{port}{protocol}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def transform_load_balancer_listener_data(load_balancer_id: str, listener_data: list[dict]) -> list[dict]:
|
|
33
|
+
"""
|
|
34
|
+
Transform load balancer listener data into a format suitable for cartography ingestion.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
load_balancer_id: The ID of the load balancer
|
|
38
|
+
listener_data: List of listener data from AWS API
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of transformed listener data
|
|
42
|
+
"""
|
|
43
|
+
transformed = []
|
|
44
|
+
for listener in listener_data:
|
|
45
|
+
listener_info = listener['Listener']
|
|
46
|
+
transformed_listener = {
|
|
47
|
+
'id': _get_listener_id(load_balancer_id, listener_info['LoadBalancerPort'], listener_info['Protocol']),
|
|
48
|
+
'port': listener_info.get('LoadBalancerPort'),
|
|
49
|
+
'protocol': listener_info.get('Protocol'),
|
|
50
|
+
'instance_port': listener_info.get('InstancePort'),
|
|
51
|
+
'instance_protocol': listener_info.get('InstanceProtocol'),
|
|
52
|
+
'policy_names': listener.get('PolicyNames', []),
|
|
53
|
+
'LoadBalancerId': load_balancer_id,
|
|
54
|
+
}
|
|
55
|
+
transformed.append(transformed_listener)
|
|
56
|
+
return transformed
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def transform_load_balancer_data(load_balancers: list[dict]) -> tuple[list[dict], list[dict]]:
|
|
60
|
+
"""
|
|
61
|
+
Transform load balancer data into a format suitable for cartography ingestion.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
load_balancers: List of load balancer data from AWS API
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Tuple of (transformed load balancer data, transformed listener data)
|
|
68
|
+
"""
|
|
69
|
+
transformed = []
|
|
70
|
+
listener_data = []
|
|
71
|
+
|
|
72
|
+
for lb in load_balancers:
|
|
73
|
+
load_balancer_id = lb['DNSName']
|
|
74
|
+
transformed_lb = {
|
|
75
|
+
'id': load_balancer_id,
|
|
76
|
+
'name': lb['LoadBalancerName'],
|
|
77
|
+
'dnsname': lb['DNSName'],
|
|
78
|
+
'canonicalhostedzonename': lb.get('CanonicalHostedZoneName'),
|
|
79
|
+
'canonicalhostedzonenameid': lb.get('CanonicalHostedZoneNameID'),
|
|
80
|
+
'scheme': lb.get('Scheme'),
|
|
81
|
+
'createdtime': str(lb['CreatedTime']),
|
|
82
|
+
'GROUP_NAME': lb.get('SourceSecurityGroup', {}).get('GroupName'),
|
|
83
|
+
'GROUP_IDS': [str(group) for group in lb.get('SecurityGroups', [])],
|
|
84
|
+
'INSTANCE_IDS': [instance['InstanceId'] for instance in lb.get('Instances', [])],
|
|
85
|
+
'LISTENER_IDS': [
|
|
86
|
+
_get_listener_id(
|
|
87
|
+
load_balancer_id,
|
|
88
|
+
listener['Listener']['LoadBalancerPort'],
|
|
89
|
+
listener['Listener']['Protocol'],
|
|
90
|
+
) for listener in lb.get('ListenerDescriptions', [])
|
|
91
|
+
],
|
|
92
|
+
}
|
|
93
|
+
transformed.append(transformed_lb)
|
|
94
|
+
|
|
95
|
+
# Classic ELB listeners are not returned anywhere else in AWS, so we must parse them out
|
|
96
|
+
# of the describe_load_balancers response.
|
|
97
|
+
if lb.get('ListenerDescriptions'):
|
|
98
|
+
listener_data.extend(
|
|
99
|
+
transform_load_balancer_listener_data(
|
|
100
|
+
load_balancer_id,
|
|
101
|
+
lb.get('ListenerDescriptions', []),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return transformed, listener_data
|
|
106
|
+
|
|
107
|
+
|
|
16
108
|
@timeit
|
|
17
109
|
@aws_handle_regions
|
|
18
|
-
def get_loadbalancer_data(boto3_session: boto3.session.Session, region: str) ->
|
|
110
|
+
def get_loadbalancer_data(boto3_session: boto3.session.Session, region: str) -> list[dict]:
|
|
19
111
|
client = boto3_session.client('elb', region_name=region, config=get_botocore_config())
|
|
20
112
|
paginator = client.get_paginator('describe_load_balancers')
|
|
21
|
-
elbs:
|
|
113
|
+
elbs: list[dict] = []
|
|
22
114
|
for page in paginator.paginate():
|
|
23
115
|
elbs.extend(page['LoadBalancerDescriptions'])
|
|
24
116
|
return elbs
|
|
25
117
|
|
|
26
118
|
|
|
27
119
|
@timeit
|
|
28
|
-
def
|
|
29
|
-
neo4j_session: neo4j.Session,
|
|
120
|
+
def load_load_balancers(
|
|
121
|
+
neo4j_session: neo4j.Session, data: list[dict], region: str, current_aws_account_id: str,
|
|
30
122
|
update_tag: int,
|
|
31
123
|
) -> None:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
l.firstseen = timestamp()
|
|
40
|
-
SET l.instance_port = data.Listener.InstancePort, l.instance_protocol = data.Listener.InstanceProtocol,
|
|
41
|
-
l.policy_names = data.PolicyNames,
|
|
42
|
-
l.lastupdated = $update_tag
|
|
43
|
-
WITH l, elb
|
|
44
|
-
MERGE (elb)-[r:ELB_LISTENER]->(l)
|
|
45
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
46
|
-
SET r.lastupdated = $update_tag
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
neo4j_session.run(
|
|
50
|
-
ingest_listener,
|
|
51
|
-
LoadBalancerId=load_balancer_id,
|
|
52
|
-
Listeners=listener_data,
|
|
53
|
-
update_tag=update_tag,
|
|
124
|
+
load(
|
|
125
|
+
neo4j_session,
|
|
126
|
+
LoadBalancerSchema(),
|
|
127
|
+
data,
|
|
128
|
+
Region=region,
|
|
129
|
+
AWS_ID=current_aws_account_id,
|
|
130
|
+
lastupdated=update_tag,
|
|
54
131
|
)
|
|
55
132
|
|
|
56
133
|
|
|
57
134
|
@timeit
|
|
58
|
-
def
|
|
59
|
-
neo4j_session: neo4j.Session,
|
|
60
|
-
update_tag: int,
|
|
61
|
-
) -> None:
|
|
62
|
-
ingest_load_balancer_subnet = """
|
|
63
|
-
MATCH (elb:LoadBalancer{id: $ID}), (subnet:EC2Subnet{subnetid: $SUBNET_ID})
|
|
64
|
-
MERGE (elb)-[r:SUBNET]->(subnet)
|
|
65
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
66
|
-
SET r.lastupdated = $update_tag
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
for subnet_id in subnets_data:
|
|
70
|
-
neo4j_session.run(
|
|
71
|
-
ingest_load_balancer_subnet,
|
|
72
|
-
ID=load_balancer_id,
|
|
73
|
-
SUBNET_ID=subnet_id,
|
|
74
|
-
update_tag=update_tag,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@timeit
|
|
79
|
-
def load_load_balancers(
|
|
80
|
-
neo4j_session: neo4j.Session, data: List[Dict], region: str, current_aws_account_id: str,
|
|
135
|
+
def load_load_balancer_listeners(
|
|
136
|
+
neo4j_session: neo4j.Session, data: list[dict], region: str, current_aws_account_id: str,
|
|
81
137
|
update_tag: int,
|
|
82
138
|
) -> None:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
MERGE (aa)-[r:RESOURCE]->(elb)
|
|
92
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
93
|
-
SET r.lastupdated = $update_tag
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
ingest_load_balancersource_security_group = """
|
|
97
|
-
MATCH (elb:LoadBalancer{id: $ID}),
|
|
98
|
-
(group:EC2SecurityGroup{name: $GROUP_NAME})
|
|
99
|
-
MERGE (elb)-[r:SOURCE_SECURITY_GROUP]->(group)
|
|
100
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
101
|
-
SET r.lastupdated = $update_tag
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
ingest_load_balancer_security_group = """
|
|
105
|
-
MATCH (elb:LoadBalancer{id: $ID}),
|
|
106
|
-
(group:EC2SecurityGroup{groupid: $GROUP_ID})
|
|
107
|
-
MERGE (elb)-[r:MEMBER_OF_EC2_SECURITY_GROUP]->(group)
|
|
108
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
109
|
-
SET r.lastupdated = $update_tag
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
ingest_instances = """
|
|
113
|
-
MATCH (elb:LoadBalancer{id: $ID}), (instance:EC2Instance{instanceid: $INSTANCE_ID})
|
|
114
|
-
MERGE (elb)-[r:EXPOSE]->(instance)
|
|
115
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
116
|
-
SET r.lastupdated = $update_tag
|
|
117
|
-
WITH instance
|
|
118
|
-
MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
119
|
-
MERGE (aa)-[r:RESOURCE]->(instance)
|
|
120
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
121
|
-
SET r.lastupdated = $update_tag
|
|
122
|
-
"""
|
|
123
|
-
|
|
124
|
-
for lb in data:
|
|
125
|
-
load_balancer_id = lb["DNSName"]
|
|
126
|
-
|
|
127
|
-
neo4j_session.run(
|
|
128
|
-
ingest_load_balancer,
|
|
129
|
-
ID=load_balancer_id,
|
|
130
|
-
CREATED_TIME=str(lb["CreatedTime"]),
|
|
131
|
-
NAME=lb["LoadBalancerName"],
|
|
132
|
-
DNS_NAME=load_balancer_id,
|
|
133
|
-
HOSTED_ZONE_NAME=lb.get("CanonicalHostedZoneName"),
|
|
134
|
-
HOSTED_ZONE_NAME_ID=lb.get("CanonicalHostedZoneNameID"),
|
|
135
|
-
SCHEME=lb.get("Scheme", ""),
|
|
136
|
-
AWS_ACCOUNT_ID=current_aws_account_id,
|
|
137
|
-
Region=region,
|
|
138
|
-
update_tag=update_tag,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
if lb["Subnets"]:
|
|
142
|
-
load_load_balancer_subnets(neo4j_session, load_balancer_id, lb["Subnets"], update_tag)
|
|
143
|
-
|
|
144
|
-
if lb["SecurityGroups"]:
|
|
145
|
-
for group in lb["SecurityGroups"]:
|
|
146
|
-
neo4j_session.run(
|
|
147
|
-
ingest_load_balancer_security_group,
|
|
148
|
-
ID=load_balancer_id,
|
|
149
|
-
GROUP_ID=str(group),
|
|
150
|
-
update_tag=update_tag,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
if lb["SourceSecurityGroup"]:
|
|
154
|
-
source_group = lb["SourceSecurityGroup"]
|
|
155
|
-
neo4j_session.run(
|
|
156
|
-
ingest_load_balancersource_security_group,
|
|
157
|
-
ID=load_balancer_id,
|
|
158
|
-
GROUP_NAME=source_group["GroupName"],
|
|
159
|
-
update_tag=update_tag,
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
if lb["Instances"]:
|
|
163
|
-
for instance in lb["Instances"]:
|
|
164
|
-
neo4j_session.run(
|
|
165
|
-
ingest_instances,
|
|
166
|
-
ID=load_balancer_id,
|
|
167
|
-
INSTANCE_ID=instance["InstanceId"],
|
|
168
|
-
AWS_ACCOUNT_ID=current_aws_account_id,
|
|
169
|
-
update_tag=update_tag,
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
if lb["ListenerDescriptions"]:
|
|
173
|
-
load_load_balancer_listeners(neo4j_session, load_balancer_id, lb["ListenerDescriptions"], update_tag)
|
|
139
|
+
load(
|
|
140
|
+
neo4j_session,
|
|
141
|
+
ELBListenerSchema(),
|
|
142
|
+
data,
|
|
143
|
+
Region=region,
|
|
144
|
+
AWS_ID=current_aws_account_id,
|
|
145
|
+
lastupdated=update_tag,
|
|
146
|
+
)
|
|
174
147
|
|
|
175
148
|
|
|
176
149
|
@timeit
|
|
177
|
-
def cleanup_load_balancers(neo4j_session: neo4j.Session, common_job_parameters:
|
|
178
|
-
|
|
150
|
+
def cleanup_load_balancers(neo4j_session: neo4j.Session, common_job_parameters: dict) -> None:
|
|
151
|
+
GraphJob.from_node_schema(ELBListenerSchema(), common_job_parameters).run(neo4j_session)
|
|
152
|
+
GraphJob.from_node_schema(LoadBalancerSchema(), common_job_parameters).run(neo4j_session)
|
|
179
153
|
|
|
180
154
|
|
|
181
155
|
@timeit
|
|
182
156
|
def sync_load_balancers(
|
|
183
|
-
neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions:
|
|
184
|
-
update_tag: int, common_job_parameters:
|
|
157
|
+
neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: list[str], current_aws_account_id: str,
|
|
158
|
+
update_tag: int, common_job_parameters: dict,
|
|
185
159
|
) -> None:
|
|
186
160
|
for region in regions:
|
|
187
161
|
logger.info("Syncing EC2 load balancers for region '%s' in account '%s'.", region, current_aws_account_id)
|
|
188
162
|
data = get_loadbalancer_data(boto3_session, region)
|
|
189
|
-
|
|
163
|
+
transformed_data, listener_data = transform_load_balancer_data(data)
|
|
164
|
+
|
|
165
|
+
load_load_balancers(neo4j_session, transformed_data, region, current_aws_account_id, update_tag)
|
|
166
|
+
load_load_balancer_listeners(neo4j_session, listener_data, region, current_aws_account_id, update_tag)
|
|
167
|
+
|
|
190
168
|
cleanup_load_balancers(neo4j_session, common_job_parameters)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
import neo4j
|
|
4
5
|
|
|
5
6
|
from cartography.config import Config
|
|
7
|
+
from cartography.graph.job import GraphJob
|
|
6
8
|
from cartography.intel.crowdstrike.endpoints import sync_hosts
|
|
7
9
|
from cartography.intel.crowdstrike.spotlight import sync_vulnerabilities
|
|
8
10
|
from cartography.intel.crowdstrike.util import get_authorization
|
|
11
|
+
from cartography.models.crowdstrike.hosts import CrowdstrikeHostSchema
|
|
9
12
|
from cartography.stats import get_stats_client
|
|
10
13
|
from cartography.util import merge_module_sync_metadata
|
|
11
14
|
from cartography.util import run_cleanup_job
|
|
@@ -50,11 +53,7 @@ def start_crowdstrike_ingestion(
|
|
|
50
53
|
config.update_tag,
|
|
51
54
|
authorization,
|
|
52
55
|
)
|
|
53
|
-
|
|
54
|
-
"crowdstrike_import_cleanup.json",
|
|
55
|
-
neo4j_session,
|
|
56
|
-
common_job_parameters,
|
|
57
|
-
)
|
|
56
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
58
57
|
|
|
59
58
|
group_id = "public"
|
|
60
59
|
if config.crowdstrike_api_url:
|
|
@@ -67,3 +66,16 @@ def start_crowdstrike_ingestion(
|
|
|
67
66
|
update_tag=config.update_tag,
|
|
68
67
|
stat_handler=stat_handler,
|
|
69
68
|
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@timeit
|
|
72
|
+
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]) -> None:
|
|
73
|
+
logger.info("Running Crowdstrike cleanup")
|
|
74
|
+
GraphJob.from_node_schema(CrowdstrikeHostSchema(), common_job_parameters).run(neo4j_session)
|
|
75
|
+
|
|
76
|
+
# Cleanup other crowdstrike assets not handled by the data model
|
|
77
|
+
run_cleanup_job(
|
|
78
|
+
"crowdstrike_import_cleanup.json",
|
|
79
|
+
neo4j_session,
|
|
80
|
+
common_job_parameters,
|
|
81
|
+
)
|
|
@@ -6,6 +6,8 @@ import neo4j
|
|
|
6
6
|
from falconpy.hosts import Hosts
|
|
7
7
|
from falconpy.oauth2 import OAuth2
|
|
8
8
|
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.models.crowdstrike.hosts import CrowdstrikeHostSchema
|
|
9
11
|
from cartography.util import timeit
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
@@ -24,55 +26,21 @@ def sync_hosts(
|
|
|
24
26
|
load_host_data(neo4j_session, host_data, update_tag)
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
@timeit
|
|
27
30
|
def load_host_data(
|
|
28
|
-
neo4j_session: neo4j.Session,
|
|
31
|
+
neo4j_session: neo4j.Session,
|
|
32
|
+
data: List[Dict],
|
|
33
|
+
update_tag: int,
|
|
29
34
|
) -> None:
|
|
30
35
|
"""
|
|
31
|
-
|
|
32
|
-
"""
|
|
33
|
-
ingestion_cypher_query = """
|
|
34
|
-
UNWIND $Hosts AS host
|
|
35
|
-
MERGE (h:CrowdstrikeHost{id: host.device_id})
|
|
36
|
-
ON CREATE SET h.cid = host.cid,
|
|
37
|
-
h.cid = host.cid,
|
|
38
|
-
h.instance_id = host.instance_id,
|
|
39
|
-
h.firstseen = timestamp()
|
|
40
|
-
SET h.status = host.status,
|
|
41
|
-
h.hostname = host.hostname,
|
|
42
|
-
h.machine_domain = host.machine_domain,
|
|
43
|
-
h.crowdstrike_first_seen = host.first_seen,
|
|
44
|
-
h.crowdstrike_last_seen = host.last_seen,
|
|
45
|
-
h.local_ip = host.local_ip,
|
|
46
|
-
h.external_ip = host.external_ip,
|
|
47
|
-
h.cpu_signature = host.cpu_signature,
|
|
48
|
-
h.bios_manufacturer = host.bios_manufacturer,
|
|
49
|
-
h.bios_version = host.bios_version,
|
|
50
|
-
h.mac_address = host.mac_address,
|
|
51
|
-
h.os_version = host.os_version,
|
|
52
|
-
h.os_build = host.os_build,
|
|
53
|
-
h.platform_id = host.platform_id,
|
|
54
|
-
h.platform_name = host.platform_name,
|
|
55
|
-
h.service_provider = host.service_provider,
|
|
56
|
-
h.service_provider_account_id = host.service_provider_account_id,
|
|
57
|
-
h.agent_version = host.agent_version,
|
|
58
|
-
h.system_manufacturer = host.system_manufacturer,
|
|
59
|
-
h.system_product_name = host.system_product_name,
|
|
60
|
-
h.product_type = host.product_type,
|
|
61
|
-
h.product_type_desc = host.product_type_desc,
|
|
62
|
-
h.provision_status = host.provision_status,
|
|
63
|
-
h.reduced_functionality_mode = host.reduced_functionality_mode,
|
|
64
|
-
h.kernel_version = host.kernel_version,
|
|
65
|
-
h.major_version = host.major_version,
|
|
66
|
-
h.minor_version = host.minor_version,
|
|
67
|
-
h.tags = host.tags,
|
|
68
|
-
h.modified_timestamp = host.modified_timestamp,
|
|
69
|
-
h.lastupdated = $update_tag
|
|
36
|
+
Load Crowdstrike host data into Neo4j.
|
|
70
37
|
"""
|
|
71
38
|
logger.info(f"Loading {len(data)} crowdstrike hosts.")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
39
|
+
load(
|
|
40
|
+
neo4j_session,
|
|
41
|
+
CrowdstrikeHostSchema(),
|
|
42
|
+
data,
|
|
43
|
+
lastupdated=update_tag,
|
|
76
44
|
)
|
|
77
45
|
|
|
78
46
|
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
from collections import namedtuple
|
|
4
4
|
from typing import Dict
|
|
5
5
|
from typing import List
|
|
6
|
+
from typing import Optional
|
|
6
7
|
from typing import Set
|
|
7
8
|
|
|
8
9
|
import googleapiclient.discovery
|
|
@@ -328,7 +329,7 @@ def _sync_multiple_projects(
|
|
|
328
329
|
|
|
329
330
|
|
|
330
331
|
@timeit
|
|
331
|
-
def get_gcp_credentials() -> GoogleCredentials:
|
|
332
|
+
def get_gcp_credentials() -> Optional[GoogleCredentials]:
|
|
332
333
|
"""
|
|
333
334
|
Gets access tokens for GCP API access.
|
|
334
335
|
:param: None
|
|
@@ -338,6 +339,7 @@ def get_gcp_credentials() -> GoogleCredentials:
|
|
|
338
339
|
# Explicitly use Application Default Credentials.
|
|
339
340
|
# See https://google-auth.readthedocs.io/en/master/user-guide.html#application-default-credentials
|
|
340
341
|
credentials, project_id = default()
|
|
342
|
+
return credentials
|
|
341
343
|
except DefaultCredentialsError as e:
|
|
342
344
|
logger.debug("Error occurred calling GoogleCredentials.get_application_default().", exc_info=True)
|
|
343
345
|
logger.error(
|
|
@@ -349,7 +351,7 @@ def get_gcp_credentials() -> GoogleCredentials:
|
|
|
349
351
|
),
|
|
350
352
|
e,
|
|
351
353
|
)
|
|
352
|
-
|
|
354
|
+
return None
|
|
353
355
|
|
|
354
356
|
|
|
355
357
|
@timeit
|
|
@@ -367,6 +369,9 @@ def start_gcp_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
367
369
|
}
|
|
368
370
|
|
|
369
371
|
credentials = get_gcp_credentials()
|
|
372
|
+
if credentials is None:
|
|
373
|
+
logger.warning("Unable to initialize GCP credentials. Skipping module.")
|
|
374
|
+
return
|
|
370
375
|
|
|
371
376
|
resources = _initialize_resources(credentials)
|
|
372
377
|
|
|
@@ -67,6 +67,14 @@ def start_gsuite_ingestion(neo4j_session: neo4j.Session, config: Config) -> None
|
|
|
67
67
|
|
|
68
68
|
creds: OAuth2Credentials | ServiceAccountCredentials
|
|
69
69
|
if config.gsuite_auth_method == 'delegated': # Legacy delegated method
|
|
70
|
+
if config.gsuite_config is None or not os.path.isfile(config.gsuite_config):
|
|
71
|
+
logger.warning(
|
|
72
|
+
(
|
|
73
|
+
"The GSuite config file is not set or is not a valid file."
|
|
74
|
+
"Skipping GSuite ingestion."
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
return
|
|
70
78
|
logger.info('Attempting to authenticate to GSuite using legacy delegated method')
|
|
71
79
|
try:
|
|
72
80
|
creds = service_account.Credentials.from_service_account_file(
|
|
@@ -25,10 +25,34 @@ def get(kandji_base_uri: str, kandji_token: str) -> List[Dict[str, Any]]:
|
|
|
25
25
|
'Authorization': f'Bearer {kandji_token}',
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
offset = 0
|
|
29
|
+
limit = 300
|
|
30
|
+
params: dict[str, str | int] = {
|
|
31
|
+
"sort": "serial_number",
|
|
32
|
+
"limit": limit,
|
|
33
|
+
"offset": offset,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
devices: List[Dict[str, Any]] = []
|
|
28
37
|
session = Session()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
while True:
|
|
39
|
+
logger.debug("Kandji device offset: %s", offset)
|
|
40
|
+
|
|
41
|
+
params["offset"] = offset
|
|
42
|
+
response = session.get(api_endpoint, headers=headers, timeout=_TIMEOUT, params=params)
|
|
43
|
+
response.raise_for_status()
|
|
44
|
+
|
|
45
|
+
result = response.json()
|
|
46
|
+
# If no more result, we are done
|
|
47
|
+
if len(result) == 0:
|
|
48
|
+
break
|
|
49
|
+
|
|
50
|
+
devices.extend(result)
|
|
51
|
+
|
|
52
|
+
offset += limit
|
|
53
|
+
|
|
54
|
+
logger.debug("Kandji device count: %d", len(devices))
|
|
55
|
+
return devices
|
|
32
56
|
|
|
33
57
|
|
|
34
58
|
@timeit
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.nodes import ExtraNodeLabels
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
8
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
9
|
+
from cartography.models.core.relationships import LinkDirection
|
|
10
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
11
|
+
from cartography.models.core.relationships import OtherRelationships
|
|
12
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class ELBListenerNodeProperties(CartographyNodeProperties):
|
|
17
|
+
id: PropertyRef = PropertyRef('id')
|
|
18
|
+
port: PropertyRef = PropertyRef('port')
|
|
19
|
+
protocol: PropertyRef = PropertyRef('protocol')
|
|
20
|
+
instance_port: PropertyRef = PropertyRef('instance_port')
|
|
21
|
+
instance_protocol: PropertyRef = PropertyRef('instance_protocol')
|
|
22
|
+
policy_names: PropertyRef = PropertyRef('policy_names')
|
|
23
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ELBListenerToLoadBalancerRelProperties(CartographyRelProperties):
|
|
28
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class ELBListenerToLoadBalancer(CartographyRelSchema):
|
|
33
|
+
target_node_label: str = 'LoadBalancer'
|
|
34
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
35
|
+
{'id': PropertyRef('LoadBalancerId')},
|
|
36
|
+
)
|
|
37
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
38
|
+
rel_label: str = "ELB_LISTENER"
|
|
39
|
+
properties: ELBListenerToLoadBalancerRelProperties = ELBListenerToLoadBalancerRelProperties()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class ELBListenerToAWSAccountRelProperties(CartographyRelProperties):
|
|
44
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class ELBListenerToAWSAccount(CartographyRelSchema):
|
|
49
|
+
target_node_label: str = 'AWSAccount'
|
|
50
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
51
|
+
{'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
|
|
52
|
+
)
|
|
53
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
54
|
+
rel_label: str = "RESOURCE"
|
|
55
|
+
properties: ELBListenerToAWSAccountRelProperties = ELBListenerToAWSAccountRelProperties()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class ELBListenerSchema(CartographyNodeSchema):
|
|
60
|
+
label: str = 'ELBListener'
|
|
61
|
+
properties: ELBListenerNodeProperties = ELBListenerNodeProperties()
|
|
62
|
+
extra_node_labels: ExtraNodeLabels = ExtraNodeLabels(['Endpoint'])
|
|
63
|
+
sub_resource_relationship: ELBListenerToAWSAccount = ELBListenerToAWSAccount()
|
|
64
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
65
|
+
[
|
|
66
|
+
ELBListenerToLoadBalancer(),
|
|
67
|
+
],
|
|
68
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
|
+
from cartography.models.core.relationships import LinkDirection
|
|
9
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import OtherRelationships
|
|
11
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class LoadBalancerNodeProperties(CartographyNodeProperties):
|
|
16
|
+
id: PropertyRef = PropertyRef('id')
|
|
17
|
+
name: PropertyRef = PropertyRef('name')
|
|
18
|
+
dnsname: PropertyRef = PropertyRef('dnsname', extra_index=True)
|
|
19
|
+
canonicalhostedzonename: PropertyRef = PropertyRef('canonicalhostedzonename')
|
|
20
|
+
canonicalhostedzonenameid: PropertyRef = PropertyRef('canonicalhostedzonenameid')
|
|
21
|
+
scheme: PropertyRef = PropertyRef('scheme')
|
|
22
|
+
region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
|
|
23
|
+
createdtime: PropertyRef = PropertyRef('createdtime')
|
|
24
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class LoadBalancerToAWSAccountRelProperties(CartographyRelProperties):
|
|
29
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class LoadBalancerToAWSAccount(CartographyRelSchema):
|
|
34
|
+
target_node_label: str = 'AWSAccount'
|
|
35
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
36
|
+
{'id': PropertyRef('AWS_ID', set_in_kwargs=True)},
|
|
37
|
+
)
|
|
38
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
39
|
+
rel_label: str = "RESOURCE"
|
|
40
|
+
properties: LoadBalancerToAWSAccountRelProperties = LoadBalancerToAWSAccountRelProperties()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class LoadBalancerToSecurityGroupRelProperties(CartographyRelProperties):
|
|
45
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class LoadBalancerToSourceSecurityGroup(CartographyRelSchema):
|
|
50
|
+
target_node_label: str = 'EC2SecurityGroup'
|
|
51
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
52
|
+
{'name': PropertyRef('GROUP_NAME')},
|
|
53
|
+
)
|
|
54
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
55
|
+
rel_label: str = "SOURCE_SECURITY_GROUP"
|
|
56
|
+
properties: LoadBalancerToSecurityGroupRelProperties = LoadBalancerToSecurityGroupRelProperties()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class LoadBalancerToEC2SecurityGroupRelProperties(CartographyRelProperties):
|
|
61
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class LoadBalancerToEC2SecurityGroup(CartographyRelSchema):
|
|
66
|
+
target_node_label: str = 'EC2SecurityGroup'
|
|
67
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
68
|
+
{'groupid': PropertyRef('GROUP_IDS', one_to_many=True)},
|
|
69
|
+
)
|
|
70
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
71
|
+
rel_label: str = "MEMBER_OF_EC2_SECURITY_GROUP"
|
|
72
|
+
properties: LoadBalancerToEC2SecurityGroupRelProperties = LoadBalancerToEC2SecurityGroupRelProperties()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class LoadBalancerToEC2InstanceRelProperties(CartographyRelProperties):
|
|
77
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class LoadBalancerToEC2Instance(CartographyRelSchema):
|
|
82
|
+
target_node_label: str = 'EC2Instance'
|
|
83
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
84
|
+
{'instanceid': PropertyRef('INSTANCE_IDS', one_to_many=True)},
|
|
85
|
+
)
|
|
86
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
87
|
+
rel_label: str = "EXPOSE"
|
|
88
|
+
properties: LoadBalancerToEC2InstanceRelProperties = LoadBalancerToEC2InstanceRelProperties()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class LoadBalancerSchema(CartographyNodeSchema):
|
|
93
|
+
label: str = 'LoadBalancer'
|
|
94
|
+
properties: LoadBalancerNodeProperties = LoadBalancerNodeProperties()
|
|
95
|
+
sub_resource_relationship: LoadBalancerToAWSAccount = LoadBalancerToAWSAccount()
|
|
96
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
97
|
+
[
|
|
98
|
+
LoadBalancerToSourceSecurityGroup(),
|
|
99
|
+
LoadBalancerToEC2SecurityGroup(),
|
|
100
|
+
LoadBalancerToEC2Instance(),
|
|
101
|
+
],
|
|
102
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
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 CrowdstrikeHostNodeProperties(CartographyNodeProperties):
|
|
10
|
+
id: PropertyRef = PropertyRef('device_id')
|
|
11
|
+
cid: PropertyRef = PropertyRef('cid')
|
|
12
|
+
instance_id: PropertyRef = PropertyRef('instance_id', extra_index=True)
|
|
13
|
+
serial_number: PropertyRef = PropertyRef('serial_number', extra_index=True)
|
|
14
|
+
status: PropertyRef = PropertyRef('status')
|
|
15
|
+
hostname: PropertyRef = PropertyRef('hostname')
|
|
16
|
+
machine_domain: PropertyRef = PropertyRef('machine_domain')
|
|
17
|
+
crowdstrike_first_seen: PropertyRef = PropertyRef('first_seen')
|
|
18
|
+
crowdstrike_last_seen: PropertyRef = PropertyRef('last_seen')
|
|
19
|
+
local_ip: PropertyRef = PropertyRef('local_ip')
|
|
20
|
+
external_ip: PropertyRef = PropertyRef('external_ip')
|
|
21
|
+
cpu_signature: PropertyRef = PropertyRef('cpu_signature')
|
|
22
|
+
bios_manufacturer: PropertyRef = PropertyRef('bios_manufacturer')
|
|
23
|
+
bios_version: PropertyRef = PropertyRef('bios_version')
|
|
24
|
+
mac_address: PropertyRef = PropertyRef('mac_address')
|
|
25
|
+
os_version: PropertyRef = PropertyRef('os_version')
|
|
26
|
+
os_build: PropertyRef = PropertyRef('os_build')
|
|
27
|
+
platform_id: PropertyRef = PropertyRef('platform_id')
|
|
28
|
+
platform_name: PropertyRef = PropertyRef('platform_name')
|
|
29
|
+
service_provider: PropertyRef = PropertyRef('service_provider')
|
|
30
|
+
service_provider_account_id: PropertyRef = PropertyRef('service_provider_account_id')
|
|
31
|
+
agent_version: PropertyRef = PropertyRef('agent_version')
|
|
32
|
+
system_manufacturer: PropertyRef = PropertyRef('system_manufacturer')
|
|
33
|
+
system_product_name: PropertyRef = PropertyRef('system_product_name')
|
|
34
|
+
product_type: PropertyRef = PropertyRef('product_type')
|
|
35
|
+
product_type_desc: PropertyRef = PropertyRef('product_type_desc')
|
|
36
|
+
provision_status: PropertyRef = PropertyRef('provision_status')
|
|
37
|
+
reduced_functionality_mode: PropertyRef = PropertyRef('reduced_functionality_mode')
|
|
38
|
+
kernel_version: PropertyRef = PropertyRef('kernel_version')
|
|
39
|
+
major_version: PropertyRef = PropertyRef('major_version')
|
|
40
|
+
minor_version: PropertyRef = PropertyRef('minor_version')
|
|
41
|
+
tags: PropertyRef = PropertyRef('tags')
|
|
42
|
+
modified_timestamp: PropertyRef = PropertyRef('modified_timestamp')
|
|
43
|
+
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class CrowdstrikeHostSchema(CartographyNodeSchema):
|
|
48
|
+
label: str = 'CrowdstrikeHost'
|
|
49
|
+
properties: CrowdstrikeHostNodeProperties = CrowdstrikeHostNodeProperties()
|
cartography/stats.py
CHANGED
|
@@ -97,7 +97,7 @@ def set_stats_client(stats_client: StatsClient) -> None:
|
|
|
97
97
|
"""
|
|
98
98
|
This is used to set the module level stats client configured to talk with a statsd host
|
|
99
99
|
"""
|
|
100
|
-
global _scoped_stats_client
|
|
100
|
+
global _scoped_stats_client # noqa: F824
|
|
101
101
|
_scoped_stats_client.set_stats_client(stats_client)
|
|
102
102
|
|
|
103
103
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cartography
|
|
3
|
-
Version: 0.101.
|
|
3
|
+
Version: 0.101.1
|
|
4
4
|
Summary: Explore assets and their relationships across your technical infrastructure.
|
|
5
5
|
Maintainer: Cartography Contributors
|
|
6
6
|
License: apache2
|
|
@@ -59,10 +59,10 @@ Requires-Dist: moto; extra == "dev"
|
|
|
59
59
|
Requires-Dist: pre-commit; extra == "dev"
|
|
60
60
|
Requires-Dist: pytest>=6.2.4; extra == "dev"
|
|
61
61
|
Requires-Dist: pytest-mock; extra == "dev"
|
|
62
|
-
Requires-Dist: pytest-cov==6.
|
|
62
|
+
Requires-Dist: pytest-cov==6.1.1; extra == "dev"
|
|
63
63
|
Requires-Dist: pytest-rerunfailures; extra == "dev"
|
|
64
64
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
65
|
-
Requires-Dist: types-requests<2.32.0.
|
|
65
|
+
Requires-Dist: types-requests<2.32.0.20250329; extra == "dev"
|
|
66
66
|
Dynamic: license-file
|
|
67
67
|
|
|
68
68
|

|
|
@@ -182,6 +182,7 @@ Get started with our [developer documentation](https://cartography-cncf.github.i
|
|
|
182
182
|
1. [MessageBird](https://messagebird.com)
|
|
183
183
|
1. [Cloudanix](https://www.cloudanix.com/)
|
|
184
184
|
1. [Corelight](https://www.corelight.com/)
|
|
185
|
+
1. [SubImage](https://subimage.io)
|
|
185
186
|
1. {Your company here} :-)
|
|
186
187
|
|
|
187
188
|
If your organization uses Cartography, please file a PR and update this list. Say hi on Slack too!
|
|
@@ -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/_version.py,sha256=
|
|
3
|
+
cartography/_version.py,sha256=XS7fJZNkjxFRd5K2jAXua6bEuLkbnwzpGqJLVBZkoGU,515
|
|
4
4
|
cartography/cli.py,sha256=-77DOKUQn3N-TDIi55V4RHLb3k36ZGZ64o1XgiT0qmE,33370
|
|
5
5
|
cartography/config.py,sha256=ZcadsKmooAkti9Kv0eDl8Ec1PcZDu3lWobtNaCnwY3k,11872
|
|
6
6
|
cartography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
cartography/stats.py,sha256
|
|
7
|
+
cartography/stats.py,sha256=-KiqrNfUe_39z9TKAQamJwKs5XePnzXscEJocAuNiJs,4420
|
|
8
8
|
cartography/sync.py,sha256=ziD63T_774gXSuD5zdz6fLGvv1Kt2ntQySSVbmcCZb8,9708
|
|
9
9
|
cartography/util.py,sha256=VZgiHcAprn3nGzItee4_TggfsGWxWPTkLN-2MIhYUqM,14999
|
|
10
10
|
cartography/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -13,7 +13,7 @@ cartography/client/aws/iam.py,sha256=dYsGikc36DEsSeR2XVOVFFUDwuU9yWj_EVkpgVYCFgM
|
|
|
13
13
|
cartography/client/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
cartography/client/core/tx.py,sha256=55Cf9DJGHHXQk4HmPOdFwr1eh9Pr1nzmIvs4XoCVr0g,10892
|
|
15
15
|
cartography/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
cartography/data/indexes.cypher,sha256=
|
|
16
|
+
cartography/data/indexes.cypher,sha256=aUHMiLPsEt09W61GyjJHfpkRJ07S2sGcpU9IReYxKC0,26551
|
|
17
17
|
cartography/data/permission_relationships.yaml,sha256=RuKGGc_3ZUQ7ag0MssB8k_zaonCkVM5E8I_svBWTmGc,969
|
|
18
18
|
cartography/data/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
cartography/data/jobs/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -85,7 +85,7 @@ cartography/data/jobs/cleanup/azure_sql_server_cleanup.json,sha256=97I2jIMBkqR3a
|
|
|
85
85
|
cartography/data/jobs/cleanup/azure_storage_account_cleanup.json,sha256=XZdjKDOjTcvn9XYScFzHts6cbtYvXETaU142N_R2qlY,5431
|
|
86
86
|
cartography/data/jobs/cleanup/azure_subscriptions_cleanup.json,sha256=bowUBCjHYlC4Xd60lv33sxRi-bv1wiT5gAOStaHMX4k,430
|
|
87
87
|
cartography/data/jobs/cleanup/azure_tenant_cleanup.json,sha256=jcjmZH6kfVGZ9q68rfvnroF0kNNHZ2uTZQ17Rmd4FH0,220
|
|
88
|
-
cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json,sha256=
|
|
88
|
+
cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json,sha256=mkC96aKS_m7THGQ3LOE_ROXxIk11moK6Fd5cSPkcYsA,1239
|
|
89
89
|
cartography/data/jobs/cleanup/digitalocean_droplet_cleanup.json,sha256=f26TdPUPYnIp45ipPys5M6VVfConUZySIbkgSr3iQno,703
|
|
90
90
|
cartography/data/jobs/cleanup/digitalocean_project_cleanup.json,sha256=5mo9vPshCdUZfgTWd_22_TLSyfe6hd41u7z-B8H1qgY,702
|
|
91
91
|
cartography/data/jobs/cleanup/gcp_compute_firewall_cleanup.json,sha256=FVNJ8EPaPhmQ_sh4vyTdMyEgs6Y-DIoFTdWJaELgz44,1904
|
|
@@ -174,9 +174,9 @@ cartography/intel/aws/ec2/images.py,sha256=SLoxcy_PQgNomVMDMdutm0zXJCOLosiHJlN63
|
|
|
174
174
|
cartography/intel/aws/ec2/instances.py,sha256=uI8eVJmeEybS8y_T8CVKAkwxJyVDCH7sbuEJYeWGSWY,12468
|
|
175
175
|
cartography/intel/aws/ec2/internet_gateways.py,sha256=dI-4-85_3DGGZZBcY_DN6XqESx9P26S6jKok314lcnQ,2883
|
|
176
176
|
cartography/intel/aws/ec2/key_pairs.py,sha256=g4imIo_5jk8upq9J4--erg-OZXG2i3cJMe6SnNCYj9s,2635
|
|
177
|
-
cartography/intel/aws/ec2/launch_templates.py,sha256=
|
|
177
|
+
cartography/intel/aws/ec2/launch_templates.py,sha256=JNCT7WKvHcM8Z6D1MDR65GsBiqmd6bMuRQAMu9BWRKY,6634
|
|
178
178
|
cartography/intel/aws/ec2/load_balancer_v2s.py,sha256=95FfQQn740gexINIHDJizOM4OKzRtQT_y2XQMipQ5Dg,8661
|
|
179
|
-
cartography/intel/aws/ec2/load_balancers.py,sha256=
|
|
179
|
+
cartography/intel/aws/ec2/load_balancers.py,sha256=ah9-lXvipzVDjGFqfpNCrEyBfdu-BdDeV2ZcPwJM78M,6013
|
|
180
180
|
cartography/intel/aws/ec2/network_acls.py,sha256=_UiOx79OxcqH0ecRjcVMglAzz5XJ4aVYLlv6dl_ism4,6809
|
|
181
181
|
cartography/intel/aws/ec2/network_interfaces.py,sha256=CzF8PooCYUQ2pk8DR8JDAhkWRUQSBj_27OsIfkL_-Cs,9199
|
|
182
182
|
cartography/intel/aws/ec2/reserved_instances.py,sha256=jv8-VLI5KL8jN1QRI20yim8lzZ7I7wR8a5EF8DckahA,3122
|
|
@@ -202,8 +202,8 @@ cartography/intel/azure/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
202
202
|
cartography/intel/azure/util/credentials.py,sha256=99PjTs0vZ2iu0tHD7TohN1VJYjuXYstfMg27F4CE0xU,7416
|
|
203
203
|
cartography/intel/bigfix/__init__.py,sha256=3LoDCm01VNNaDRGsRiykJm1GJgUQ8zI1HO6NodLFVIA,1058
|
|
204
204
|
cartography/intel/bigfix/computers.py,sha256=HAwA-muDBLu2xkFFL2Ae-xjCH1gxKKwxGMZM4BE_Qdo,5909
|
|
205
|
-
cartography/intel/crowdstrike/__init__.py,sha256=
|
|
206
|
-
cartography/intel/crowdstrike/endpoints.py,sha256=
|
|
205
|
+
cartography/intel/crowdstrike/__init__.py,sha256=qDFCtNzUSnvMZFFoLjRAPK3pbKpIO6BaerY3bDyEASc,2397
|
|
206
|
+
cartography/intel/crowdstrike/endpoints.py,sha256=MVuUw7yXesJmQL79lbPxOnHjO48lD3WCH5WzL5asKUU,2144
|
|
207
207
|
cartography/intel/crowdstrike/spotlight.py,sha256=yNhj44-RYF6ubck-hHMKhKiNU0fCfhQf4Oagopc31EM,4754
|
|
208
208
|
cartography/intel/crowdstrike/util.py,sha256=gfJ6Ptr6YdbBS9Qj9a_-Jc-IJroADDRcXqjh5TW0qXE,277
|
|
209
209
|
cartography/intel/cve/__init__.py,sha256=u9mv5O_qkSLmdhLhLm1qbwmhoeLQ3A3fQTjNyLQpEyI,3656
|
|
@@ -220,7 +220,7 @@ cartography/intel/duo/phones.py,sha256=ueJheqSLD2xYcMus5eOiixPYS3_xVjgQzeomjV2a6
|
|
|
220
220
|
cartography/intel/duo/tokens.py,sha256=bEEnjfc4waQnkRHVSnZLAeGE8wHOOZL7FA9m80GGQdQ,2396
|
|
221
221
|
cartography/intel/duo/users.py,sha256=lc7ly_XKeUjJ50szw31WT_GiCrZfGKJv1zVUpmTchh4,4097
|
|
222
222
|
cartography/intel/duo/web_authn_credentials.py,sha256=IbDf3CWqfEyI7f9zJugUvoDd6vZOECfb_7ANZaRYzuk,2636
|
|
223
|
-
cartography/intel/gcp/__init__.py,sha256=
|
|
223
|
+
cartography/intel/gcp/__init__.py,sha256=sZHPfDCPZFCE5d6aj20Ow4AC0vrFxV7RCn_cMinCDmI,17650
|
|
224
224
|
cartography/intel/gcp/compute.py,sha256=CH2cBdOwbLZCAbkfRJkkI-sFybXVKRWEUGDJANQmvyA,48333
|
|
225
225
|
cartography/intel/gcp/crm.py,sha256=Uw5PILhVFhpM8gq7uu2v7F_YikDW3gsTZ3d7-e8Z1_k,12324
|
|
226
226
|
cartography/intel/gcp/dns.py,sha256=y2pvbmV04cnrMyuu_nbW3oc7uwHX6yEzn1n7veCsjmk,7748
|
|
@@ -232,13 +232,13 @@ cartography/intel/github/repos.py,sha256=MmpxZASDJFQxDeSMxX3pZcpxCHFPos4_uYC_cX9
|
|
|
232
232
|
cartography/intel/github/teams.py,sha256=AltQSmBHHmyzBtnRkez9Bo5yChEKBSt3wwzhGcfqmX4,14180
|
|
233
233
|
cartography/intel/github/users.py,sha256=MCLE0V0UCzQm3k3KmrNe6PYkI6usRQZYy2rCN3mT8o0,8948
|
|
234
234
|
cartography/intel/github/util.py,sha256=K0cXOPuhnGvN-aqcSUBO3vTuKQLjufVal9kn2HwOpbo,8110
|
|
235
|
-
cartography/intel/gsuite/__init__.py,sha256=
|
|
235
|
+
cartography/intel/gsuite/__init__.py,sha256=S8LxQxQ6CP7g2U5x-XnJFz17OmAgvGdzyhA-CZqg-_g,5724
|
|
236
236
|
cartography/intel/gsuite/api.py,sha256=bx0mPn6x6fgssxgm343NHdwjbtFkO6SZTucOsoW0Hgk,11143
|
|
237
237
|
cartography/intel/jamf/__init__.py,sha256=Nof-LrUeevoieo6oP2GyfTwx8k5TUIgreW6hSj53YjQ,419
|
|
238
238
|
cartography/intel/jamf/computers.py,sha256=EfjlupQ-9HYTjOrmuwrGuJDy9ApAnJvk8WrYcp6_Jkk,1673
|
|
239
239
|
cartography/intel/jamf/util.py,sha256=EAyP8VpOY2uAvW3HtX6r7qORNjGa1Tr3fuqezuLQ0j4,1017
|
|
240
240
|
cartography/intel/kandji/__init__.py,sha256=Y38bVRmrGVJRy0mSof8xU-cuEyJ7N_oI7KekYjYyuiQ,1076
|
|
241
|
-
cartography/intel/kandji/devices.py,sha256=
|
|
241
|
+
cartography/intel/kandji/devices.py,sha256=bRePUC0xFeaST3PQdJ6Af2MdTeFZuIVq4r7CY5YEid0,2797
|
|
242
242
|
cartography/intel/kubernetes/__init__.py,sha256=jaOTEanWnTrYvcBN1XUC5oqBhz1AJbFmzoT9uu_VBSg,1481
|
|
243
243
|
cartography/intel/kubernetes/namespaces.py,sha256=6o-FgAX_Ai5NCj2xOWM-RNWEvn0gZjVQnZSGCJlcIhw,2710
|
|
244
244
|
cartography/intel/kubernetes/pods.py,sha256=aX3pP_vs6icMe2vK4vgMak6HZ64okhRzoihpkPHscGU,4502
|
|
@@ -296,6 +296,8 @@ cartography/models/aws/ec2/keypair_instance.py,sha256=M1Ru8Z_2izW0cADAnQVVHaKsT_
|
|
|
296
296
|
cartography/models/aws/ec2/launch_configurations.py,sha256=zdfWJEx93HXDXd_IzSEkhvcztkJI7_v_TCE_d8ZNAyI,2764
|
|
297
297
|
cartography/models/aws/ec2/launch_template_versions.py,sha256=RitfnAuAj0XpFsCXkRbtUhHMAi8Vsvmtury231eKvGU,3897
|
|
298
298
|
cartography/models/aws/ec2/launch_templates.py,sha256=GqiwFuMp72LNSt2eQlp2WfdU_vHsom-xKV5AaUewSHQ,2157
|
|
299
|
+
cartography/models/aws/ec2/load_balancer_listeners.py,sha256=M7oYOinOQkEUijJjhs1oCffB5VmLWYSa92tVWKwMSJQ,2879
|
|
300
|
+
cartography/models/aws/ec2/load_balancers.py,sha256=qJbPWePdO2vuyKzcVcSvKtHlEFMBkkUovZ826BaAcwg,4347
|
|
299
301
|
cartography/models/aws/ec2/loadbalancerv2.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
300
302
|
cartography/models/aws/ec2/network_acl_rules.py,sha256=4Rq2J-Dce8J6y9J6YIalmYtuQRWLp652LXO1Xg6XGPE,3951
|
|
301
303
|
cartography/models/aws/ec2/network_acls.py,sha256=pJKsXdMLB8L79lmTYpLJfFJ6p7PWpf3rBN6eW6y-5hY,3419
|
|
@@ -329,6 +331,8 @@ cartography/models/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
329
331
|
cartography/models/core/common.py,sha256=Bk0dCBkO9Udj9v4e8WE9mgBGaZTI170m9QpoiiYoz48,6206
|
|
330
332
|
cartography/models/core/nodes.py,sha256=h5dwBOk_a2uCHZWeQz3pidr7gkqMKf7buIZgl6M1Ox4,3699
|
|
331
333
|
cartography/models/core/relationships.py,sha256=6AwXvk0dq48BxqyxBpHyBXZ3dJNm65t1y4vNg4n25uA,5103
|
|
334
|
+
cartography/models/crowdstrike/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
335
|
+
cartography/models/crowdstrike/hosts.py,sha256=5PiDAhCPWDUd9kE61FliaPzsuIQBZRIQ87eHMgQF_M4,2672
|
|
332
336
|
cartography/models/cve/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
333
337
|
cartography/models/cve/cve.py,sha256=zaCLcGuEL2EnScExjbmpPBNVBivMBKnnRajCYL0LJFE,3720
|
|
334
338
|
cartography/models/cve/cve_feed.py,sha256=A_h9ET2vQYGpgMr8_--iqdQfF5ZyUNM6aQDruUuA5OU,740
|
|
@@ -360,9 +364,9 @@ cartography/models/snipeit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
360
364
|
cartography/models/snipeit/asset.py,sha256=FyRAaeXuZjMy0eUQcSDFcgEAF5lbLMlvqp1Tv9d3Lv4,3238
|
|
361
365
|
cartography/models/snipeit/tenant.py,sha256=p4rFnpNNuF1W5ilGBbexDaETWTwavfb38RcQGoImkQI,679
|
|
362
366
|
cartography/models/snipeit/user.py,sha256=MsB4MiCVNTH6JpESime7cOkB89autZOXQpL6Z0l7L6o,2113
|
|
363
|
-
cartography-0.101.
|
|
364
|
-
cartography-0.101.
|
|
365
|
-
cartography-0.101.
|
|
366
|
-
cartography-0.101.
|
|
367
|
-
cartography-0.101.
|
|
368
|
-
cartography-0.101.
|
|
367
|
+
cartography-0.101.1.dist-info/licenses/LICENSE,sha256=kvLEBRYaQ1RvUni6y7Ti9uHeooqnjPoo6n_-0JO1ETc,11351
|
|
368
|
+
cartography-0.101.1.dist-info/METADATA,sha256=_H3KYZBGizgjTNVWB6poKHrcpAg_0Ftt8ltY6E91sZ4,11906
|
|
369
|
+
cartography-0.101.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
370
|
+
cartography-0.101.1.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
|
|
371
|
+
cartography-0.101.1.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
|
|
372
|
+
cartography-0.101.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|