assemblyline-core 4.5.1.dev38__tar.gz → 4.5.1.dev90__tar.gz
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 assemblyline-core might be problematic. Click here for more details.
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/PKG-INFO +1 -1
- assemblyline-core-4.5.1.dev90/assemblyline_core/VERSION +1 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/archiver/run_archiver.py +20 -3
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/heartbeat_formatter.py +53 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/metrics_server.py +70 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/docker_ctl.py +12 -1
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/kubernetes_ctl.py +2 -13
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/helper.py +93 -96
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/run_updater.py +3 -3
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/worker.py +1 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/PKG-INFO +1 -1
- assemblyline-core-4.5.1.dev38/assemblyline_core/VERSION +0 -1
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/LICENCE.md +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/README.md +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/alerter/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/alerter/processing.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/alerter/run_alerter.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/archiver/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/badlist_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/__main__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/dispatcher.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/schedules.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/timeout.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/expiry/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/expiry/run_expiry.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/__main__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/constants.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/ingester.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/es_metrics.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/helper.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/run_heartbeat_manager.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/run_metrics_aggregator.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/run_statistics_aggregator.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/plumber/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/plumber/run_plumber.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/creator/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/creator/run.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/creator/run_worker.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/loader/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/loader/run.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/loader/run_worker.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/replay.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/safelist_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/collection.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/interface.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/run_scaler.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/scaler_server.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/server_base.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/signature_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/submission_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/tasking_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/crawler.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/department_map.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/safelist.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/stream_map.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/workflow/__init__.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/workflow/run_workflow.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/SOURCES.txt +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/dependency_links.txt +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/requires.txt +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/top_level.txt +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/setup.cfg +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/setup.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_alerter.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_badlist_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_dispatcher.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_expiry.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_plumber.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_replay.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_safelist_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_scaler.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_scheduler.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_signature_client.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_simulation.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_vacuum.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_worker_ingest.py +0 -0
- {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_worker_submit.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.5.1.dev90
|
|
@@ -7,8 +7,8 @@ from assemblyline.common import forge
|
|
|
7
7
|
from assemblyline.common.archiving import ARCHIVE_QUEUE_NAME
|
|
8
8
|
from assemblyline.common.metrics import MetricsFactory
|
|
9
9
|
from assemblyline.datastore.collection import ESCollection, Index
|
|
10
|
+
from assemblyline.datastore.exceptions import VersionConflictException
|
|
10
11
|
from assemblyline.odm.messages.archive_heartbeat import Metrics
|
|
11
|
-
from assemblyline.odm.models.submission import Submission
|
|
12
12
|
from assemblyline.remote.datatypes import get_client
|
|
13
13
|
from assemblyline.remote.datatypes.queues.named import NamedQueue
|
|
14
14
|
|
|
@@ -62,7 +62,12 @@ class Archiver(ServerBase):
|
|
|
62
62
|
return
|
|
63
63
|
else:
|
|
64
64
|
try:
|
|
65
|
-
|
|
65
|
+
if len(message) == 3:
|
|
66
|
+
archive_type, type_id, delete_after = message
|
|
67
|
+
metadata = None
|
|
68
|
+
else:
|
|
69
|
+
archive_type, type_id, delete_after, metadata = message
|
|
70
|
+
|
|
66
71
|
self.counter.increment('received')
|
|
67
72
|
except Exception:
|
|
68
73
|
self.log.error(f"Invalid message received: {message}")
|
|
@@ -76,7 +81,19 @@ class Archiver(ServerBase):
|
|
|
76
81
|
if archive_type == "submission":
|
|
77
82
|
self.counter.increment('submission')
|
|
78
83
|
# Load submission
|
|
79
|
-
|
|
84
|
+
while True:
|
|
85
|
+
try:
|
|
86
|
+
submission, version = self.datastore.submission.get_if_exists(type_id, version=True)
|
|
87
|
+
|
|
88
|
+
# If we have metadata passed in the message, we need to apply it before archiving the submission
|
|
89
|
+
if metadata and self.config.core.archiver.use_metadata:
|
|
90
|
+
submission.metadata.update({f"archive.{k}": v for k, v in metadata.items()})
|
|
91
|
+
self.datastore.submission.save(type_id, submission, version=version)
|
|
92
|
+
|
|
93
|
+
break
|
|
94
|
+
except VersionConflictException as vce:
|
|
95
|
+
self.log.info(f"Retrying saving metadata due to version conflict: {str(vce)}")
|
|
96
|
+
|
|
80
97
|
if not submission:
|
|
81
98
|
raise SubmissionNotFound(type_id)
|
|
82
99
|
|
|
@@ -7,6 +7,7 @@ from assemblyline.common.archiving import ARCHIVE_QUEUE_NAME
|
|
|
7
7
|
from assemblyline.common.constants import DISPATCH_TASK_HASH, SUBMISSION_QUEUE, \
|
|
8
8
|
SERVICE_STATE_HASH, ServiceStatus
|
|
9
9
|
from assemblyline.datastore.exceptions import SearchException
|
|
10
|
+
from assemblyline.odm.messages.retrohunt_heartbeat import RetrohuntMessage
|
|
10
11
|
from assemblyline.odm.messages.scaler_heartbeat import ScalerMessage
|
|
11
12
|
from assemblyline.odm.messages.scaler_status_heartbeat import ScalerStatusMessage
|
|
12
13
|
from assemblyline.odm.messages.alerter_heartbeat import AlerterMessage
|
|
@@ -15,6 +16,7 @@ from assemblyline.odm.messages.dispatcher_heartbeat import DispatcherMessage
|
|
|
15
16
|
from assemblyline.odm.messages.expiry_heartbeat import ExpiryMessage
|
|
16
17
|
from assemblyline.odm.messages.ingest_heartbeat import IngestMessage
|
|
17
18
|
from assemblyline.odm.messages.service_heartbeat import ServiceMessage
|
|
19
|
+
from assemblyline.odm.messages.elastic_heartbeat import ElasticMessage
|
|
18
20
|
from assemblyline.odm.messages.vacuum_heartbeat import VacuumMessage
|
|
19
21
|
from assemblyline.remote.datatypes import get_client
|
|
20
22
|
from assemblyline.remote.datatypes.hash import Hash, ExpiringHash
|
|
@@ -289,5 +291,56 @@ class HeartbeatFormatter(object):
|
|
|
289
291
|
except Exception:
|
|
290
292
|
self.log.exception("An exception occurred while generating VacuumMessage")
|
|
291
293
|
|
|
294
|
+
elif m_type == "elastic":
|
|
295
|
+
try:
|
|
296
|
+
msg = {
|
|
297
|
+
"sender": self.sender,
|
|
298
|
+
"msg": {
|
|
299
|
+
"instances": instances,
|
|
300
|
+
'unassigned_shards': m_data['unassigned'],
|
|
301
|
+
"request_time": m_data['request_time'],
|
|
302
|
+
"shard_sizes": [{'name': index, 'shard_size': size}
|
|
303
|
+
for index, size in m_data['shard_sizes'].items()],
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
self.status_queue.publish(ElasticMessage(msg).as_primitives())
|
|
307
|
+
self.log.info(f"Sent elastic heartbeat: {msg['msg']}")
|
|
308
|
+
except Exception:
|
|
309
|
+
self.log.exception("An exception occurred while generating ElasticMessage")
|
|
310
|
+
|
|
311
|
+
elif m_type == "retrohunt":
|
|
312
|
+
try:
|
|
313
|
+
|
|
314
|
+
status = m_data.get('status', {})
|
|
315
|
+
fetcher = status.get('fetcher', {})
|
|
316
|
+
resources = status.get('resources', {})
|
|
317
|
+
storage = status.get('storage', {})
|
|
318
|
+
last_minute_cpu = sum(report.get('cpu_load_1m', 0) for report in resources.values())
|
|
319
|
+
memory = sum(report.get('memory', 0) for report in resources.values())
|
|
320
|
+
free_storage = [report.get('high_water', 0) for report in storage.values()]
|
|
321
|
+
min_storage = sum_storage = 0
|
|
322
|
+
if free_storage:
|
|
323
|
+
min_storage = min(free_storage)
|
|
324
|
+
sum_storage = sum(free_storage)
|
|
325
|
+
|
|
326
|
+
msg = {
|
|
327
|
+
"sender": self.sender,
|
|
328
|
+
"msg": {
|
|
329
|
+
'instances': instances,
|
|
330
|
+
'request_time': m_data['request_time'],
|
|
331
|
+
'pending_files': fetcher.get('pending_files', 0),
|
|
332
|
+
'ingested_last_minute': fetcher.get('last_minute_throughput', 0),
|
|
333
|
+
'worker_storage_available': min_storage,
|
|
334
|
+
'total_storage_available': sum_storage,
|
|
335
|
+
'active_searches': status.get('active_searches', 0),
|
|
336
|
+
'last_minute_cpu': last_minute_cpu,
|
|
337
|
+
'total_memory_used': memory,
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
self.status_queue.publish(RetrohuntMessage(msg).as_primitives())
|
|
341
|
+
self.log.info(f"Sent retrohunt heartbeat: {msg['msg']}")
|
|
342
|
+
except Exception:
|
|
343
|
+
self.log.exception("An exception occurred while generating RetrohuntMessage")
|
|
344
|
+
|
|
292
345
|
else:
|
|
293
346
|
self.log.warning(f"Skipping unknown counter: {m_name} [{m_type}] ==> {m_data}")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import tempfile
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
+
import threading
|
|
6
7
|
from collections import Counter
|
|
7
8
|
from threading import Lock, Thread
|
|
8
9
|
from os import environ, path
|
|
@@ -259,6 +260,7 @@ class HeartbeatManager(ServerBase):
|
|
|
259
260
|
self.metrics_queue = CommsQueue(METRICS_QUEUE)
|
|
260
261
|
self.scheduler = BackgroundScheduler(daemon=True)
|
|
261
262
|
self.hm = HeartbeatFormatter("heartbeat_manager", self.log, config=self.config)
|
|
263
|
+
self.hauntedhouse_client = forge.get_hauntedhouse_client(self.config)
|
|
262
264
|
|
|
263
265
|
self.counters_lock = Lock()
|
|
264
266
|
self.counters = {}
|
|
@@ -281,6 +283,10 @@ class HeartbeatManager(ServerBase):
|
|
|
281
283
|
self.scheduler.add_job(self._export_hearbeats, 'interval', seconds=self.config.core.metrics.export_interval)
|
|
282
284
|
self.scheduler.start()
|
|
283
285
|
|
|
286
|
+
threading.Thread(target=self._call_interval, args=('es_shards', self._fetch_shards), daemon=True).start()
|
|
287
|
+
if self.config.retrohunt.enabled:
|
|
288
|
+
threading.Thread(target=self._call_interval, args=('retrohunt', self._fetch_retrohunt), daemon=True).start()
|
|
289
|
+
|
|
284
290
|
while self.running:
|
|
285
291
|
for msg in self.metrics_queue.listen():
|
|
286
292
|
# APM Transaction start
|
|
@@ -316,6 +322,70 @@ class HeartbeatManager(ServerBase):
|
|
|
316
322
|
if self.apm_client:
|
|
317
323
|
self.apm_client.end_transaction('process_message', 'success')
|
|
318
324
|
|
|
325
|
+
def _call_interval(self, name, method):
|
|
326
|
+
while self.running:
|
|
327
|
+
# Run the heartbeat
|
|
328
|
+
send_time = time.time()
|
|
329
|
+
try:
|
|
330
|
+
method()
|
|
331
|
+
except Exception:
|
|
332
|
+
self.log.exception('Error running metric fetcher %s', name)
|
|
333
|
+
|
|
334
|
+
# Wait until we are inline with the heartbeat interval
|
|
335
|
+
elapsed = time.time() - send_time
|
|
336
|
+
remaining = self.config.core.metrics.export_interval - elapsed
|
|
337
|
+
if remaining > 0:
|
|
338
|
+
self.sleep(remaining)
|
|
339
|
+
|
|
340
|
+
def _fetch_shards(self):
|
|
341
|
+
request_time = None
|
|
342
|
+
sizes = {}
|
|
343
|
+
unassigned = 0
|
|
344
|
+
nodes = set()
|
|
345
|
+
try:
|
|
346
|
+
# Pull shard data from elastisearch
|
|
347
|
+
start_time = time.time()
|
|
348
|
+
response = self.datastore.ds.client.cat.shards(bytes='b', format='json', master_timeout='5s')
|
|
349
|
+
for shard in response.body:
|
|
350
|
+
index = shard['index']
|
|
351
|
+
node = shard['node']
|
|
352
|
+
if node:
|
|
353
|
+
sizes.setdefault(index, 0)
|
|
354
|
+
sizes[index] = max(sizes[index], int(shard.get('store') or 0))
|
|
355
|
+
nodes.add(node)
|
|
356
|
+
else:
|
|
357
|
+
unassigned += 1
|
|
358
|
+
request_time = time.time() - start_time
|
|
359
|
+
|
|
360
|
+
finally:
|
|
361
|
+
# Export metrics heartbeat, send None and empty if an error occurs while getting the data
|
|
362
|
+
# the error will be logged in the outer function
|
|
363
|
+
metrics = {
|
|
364
|
+
'shard_sizes': sizes,
|
|
365
|
+
'request_time': request_time,
|
|
366
|
+
'unassigned': unassigned
|
|
367
|
+
}
|
|
368
|
+
self.hm.send_heartbeat('elastic', 'datastore', metrics, len(nodes))
|
|
369
|
+
|
|
370
|
+
def _fetch_retrohunt(self):
|
|
371
|
+
status = {}
|
|
372
|
+
request_time = None
|
|
373
|
+
try:
|
|
374
|
+
# Pull status message from retrohunt
|
|
375
|
+
start_time = time.time()
|
|
376
|
+
status = self.hauntedhouse_client.status()
|
|
377
|
+
request_time = time.time() - start_time
|
|
378
|
+
|
|
379
|
+
finally:
|
|
380
|
+
metrics = {
|
|
381
|
+
'request_time': request_time,
|
|
382
|
+
'status': status,
|
|
383
|
+
}
|
|
384
|
+
instances = len(status.get('storage', {}))
|
|
385
|
+
|
|
386
|
+
# Export heartbeat
|
|
387
|
+
self.hm.send_heartbeat('retrohunt', 'hauntedhouse', metrics, instances)
|
|
388
|
+
|
|
319
389
|
def _export_hearbeats(self):
|
|
320
390
|
try:
|
|
321
391
|
self.heartbeat()
|
|
@@ -121,7 +121,10 @@ class DockerController(ControllerInterface):
|
|
|
121
121
|
aliases=['service-server'])
|
|
122
122
|
|
|
123
123
|
# As long as the current service server is still running, just block its exit code in this thread
|
|
124
|
-
|
|
124
|
+
try:
|
|
125
|
+
self.service_server.wait()
|
|
126
|
+
except docker.errors.NotFound:
|
|
127
|
+
pass
|
|
125
128
|
|
|
126
129
|
# If it does return, find the new service server
|
|
127
130
|
self.service_server = self.find_service_server()
|
|
@@ -158,6 +161,14 @@ class DockerController(ControllerInterface):
|
|
|
158
161
|
if 'already exists' in str(e):
|
|
159
162
|
return
|
|
160
163
|
raise e
|
|
164
|
+
except docker.errors.NotFound as e:
|
|
165
|
+
if aliases == ['service-server']:
|
|
166
|
+
# We've lost our service-server container, time to find another
|
|
167
|
+
self.service_server = self.find_service_server()
|
|
168
|
+
network.connect(self.service_server, aliases=aliases)
|
|
169
|
+
return
|
|
170
|
+
raise e
|
|
171
|
+
|
|
161
172
|
|
|
162
173
|
def _start(self, service_name):
|
|
163
174
|
"""Launch a docker container in a manner suitable for Assemblyline."""
|
|
@@ -21,7 +21,7 @@ from assemblyline.odm.models.config import Selector
|
|
|
21
21
|
|
|
22
22
|
from kubernetes import client, config, watch
|
|
23
23
|
from kubernetes.client import V1Deployment, V1DeploymentSpec, V1PodTemplateSpec, V1DeploymentStrategy, \
|
|
24
|
-
V1PodSpec,
|
|
24
|
+
V1PodSpec, V1ObjectMeta, V1Volume, V1Container, V1VolumeMount, V1EnvVar, V1ConfigMapVolumeSource, \
|
|
25
25
|
V1PersistentVolumeClaimVolumeSource, V1LabelSelector, V1ResourceRequirements, V1PersistentVolumeClaim, \
|
|
26
26
|
V1PersistentVolumeClaimSpec, V1NetworkPolicy, V1NetworkPolicySpec, V1NetworkPolicyEgressRule, V1NetworkPolicyPeer, \
|
|
27
27
|
V1NetworkPolicyIngressRule, V1Secret, V1SecretVolumeSource, V1LocalObjectReference, V1Service, \
|
|
@@ -839,25 +839,14 @@ class KubernetesController(ControllerInterface):
|
|
|
839
839
|
volume_mounts=chown_mounts
|
|
840
840
|
))
|
|
841
841
|
|
|
842
|
-
pod_os = None
|
|
843
|
-
security_context = V1PodSecurityContext(fs_group=1000)
|
|
844
|
-
if docker_config.operating_system:
|
|
845
|
-
# Allow Kubernetes to schedule the pod to a compatible node
|
|
846
|
-
pod_os = V1PodOS(name=docker_config.operating_system)
|
|
847
|
-
|
|
848
|
-
if docker_config.operating_system == 'windows':
|
|
849
|
-
security_context = None
|
|
850
|
-
|
|
851
|
-
|
|
852
842
|
pod = V1PodSpec(
|
|
853
843
|
init_containers=init_containers,
|
|
854
844
|
volumes=all_volumes,
|
|
855
845
|
containers=self._create_containers(service_name, deployment_name, docker_config,
|
|
856
846
|
all_mounts, core_container=core_mounts),
|
|
857
|
-
os=pod_os,
|
|
858
847
|
priority_class_name=self.dependency_priority if high_priority else self.priority,
|
|
859
848
|
termination_grace_period_seconds=shutdown_seconds,
|
|
860
|
-
security_context=
|
|
849
|
+
security_context=V1PodSecurityContext(fs_group=1000),
|
|
861
850
|
service_account_name=service_account,
|
|
862
851
|
affinity=selector_to_node_affinity(self.linux_node_selector),
|
|
863
852
|
)
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/helper.py
RENAMED
|
@@ -12,114 +12,95 @@ from base64 import b64encode
|
|
|
12
12
|
from collections import defaultdict
|
|
13
13
|
from logging import Logger
|
|
14
14
|
from packaging.version import parse, Version
|
|
15
|
-
from typing import Any, Dict, List, Tuple
|
|
16
15
|
|
|
17
16
|
DEFAULT_DOCKER_REGISTRY = "registry.hub.docker.com"
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class ContainerRegistry():
|
|
21
|
-
def __init__(self, server, headers: Dict[str, str] = None, verify: bool = True,
|
|
22
|
-
proxies: Dict[str, str] = None, *args, **kwargs):
|
|
23
|
-
self.server = server
|
|
24
|
-
self.session = requests.Session()
|
|
25
|
-
self.session.headers = headers
|
|
26
|
-
self.session.verify = verify
|
|
27
|
-
self.session.proxies = proxies
|
|
28
|
-
|
|
29
|
-
def _make_request(self, path: str) -> Dict[str, Any]:
|
|
30
|
-
request_path = f"{self.server}{path}"
|
|
31
|
-
resp = None
|
|
32
|
-
try:
|
|
33
|
-
resp = self.session.get(f"https://{request_path}")
|
|
34
|
-
except requests.exceptions.SSLError:
|
|
35
|
-
# Connect to insecure registry over HTTP (development only)
|
|
36
|
-
if not self.session.verify:
|
|
37
|
-
resp = self.session.get(f"http://{request_path}")
|
|
38
|
-
# Test for valid response
|
|
39
|
-
if resp and resp.ok:
|
|
40
|
-
return resp.json()
|
|
41
|
-
return None
|
|
42
|
-
|
|
43
20
|
# Provide a means of obtaining a list of tags from a container registry
|
|
44
|
-
def
|
|
45
|
-
raise NotImplementedError()
|
|
46
|
-
|
|
47
|
-
# Provide a means of obtaining the compatible operating system for the container image
|
|
48
|
-
def get_image_os(self, image_name, image_tag) -> str:
|
|
21
|
+
def _get_proprietary_registry_tags(self, server, image_name, auth, verify, proxies=None, token_server=None):
|
|
49
22
|
raise NotImplementedError()
|
|
50
23
|
|
|
51
24
|
|
|
52
|
-
class DockerHub(ContainerRegistry):
|
|
53
|
-
def __init__(self, update_channel, proxies: Dict[str, str] = None, *args, **kwargs):
|
|
54
|
-
super().__init__(DEFAULT_DOCKER_REGISTRY, None, True, proxies)
|
|
55
|
-
self.update_channel = update_channel
|
|
56
|
-
|
|
57
|
-
def get_image_tags(self, image_name) -> List[str]:
|
|
58
|
-
resp = self._make_request(f"/v2/repositories/{image_name}/tags?page_size=5&page=1&name={self.update_channel}")
|
|
59
|
-
if resp:
|
|
60
|
-
return [x['name'] for x in resp['results']]
|
|
61
|
-
return []
|
|
62
|
-
|
|
63
|
-
def get_image_os(self, image_name, image_tag) -> str:
|
|
64
|
-
resp = self._make_request(f"/v2/repositories/{image_name}/tags/{image_tag}")
|
|
65
|
-
if resp:
|
|
66
|
-
return resp['images'][0]['os']
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# Ref: https://docs.docker.com/registry/spec/api/#detail
|
|
71
25
|
class DockerRegistry(ContainerRegistry):
|
|
72
|
-
def
|
|
26
|
+
def _get_proprietary_registry_tags(self, server, image_name, auth, verify, proxies=None, token_server=None):
|
|
73
27
|
# Find latest tag for each types
|
|
74
|
-
|
|
75
|
-
if resp:
|
|
76
|
-
return resp['tags'] or []
|
|
77
|
-
return []
|
|
28
|
+
url = f"https://{server}/v2/{image_name}/tags/list"
|
|
78
29
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if
|
|
82
|
-
|
|
83
|
-
|
|
30
|
+
# Get tag list
|
|
31
|
+
headers = {}
|
|
32
|
+
if auth:
|
|
33
|
+
headers["Authorization"] = auth
|
|
34
|
+
else:
|
|
35
|
+
# Retrieve token for authentication: https://distribution.github.io/distribution/spec/auth/token/
|
|
84
36
|
|
|
85
|
-
|
|
86
|
-
|
|
37
|
+
# Assume the token server is the same as the container image registry host if not explicitly set
|
|
38
|
+
token_server = token_server if token_server else server
|
|
39
|
+
token_url = f"https://{token_server}/token?scope=repository:{image_name}:pull"
|
|
40
|
+
resp = requests.get(token_url)
|
|
41
|
+
if resp.ok:
|
|
42
|
+
# Request to obtain token was successful, set Authorization header for registry API
|
|
43
|
+
token = resp.json().get('token')
|
|
44
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
87
45
|
|
|
46
|
+
resp = None
|
|
47
|
+
try:
|
|
48
|
+
resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
|
|
49
|
+
except requests.exceptions.SSLError:
|
|
50
|
+
# Connect to insecure registry over HTTP (development only)
|
|
51
|
+
if not verify:
|
|
52
|
+
url = f"http://{server}/v2/{image_name}/tags/list"
|
|
53
|
+
resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
|
|
54
|
+
# Test for valid response
|
|
55
|
+
if resp and resp.ok:
|
|
56
|
+
# Test for positive list of tags
|
|
57
|
+
resp_data = resp.json()
|
|
58
|
+
return resp_data['tags'] or []
|
|
59
|
+
return []
|
|
88
60
|
|
|
89
|
-
# Ref: https://github.com/goharbor/harbor/blob/main/api/v2.0/swagger.yaml
|
|
90
61
|
class HarborRegistry(ContainerRegistry):
|
|
91
|
-
def
|
|
62
|
+
def _get_proprietary_registry_tags(self, server, image_name, auth, verify, proxies=None, token_server=None):
|
|
92
63
|
# Determine project/repo IDs from image name
|
|
93
64
|
project_id, repo_id = image_name.split('/', 1)
|
|
94
65
|
repo_id = repo_id.replace('/', "%2F")
|
|
95
|
-
|
|
66
|
+
url = f"https://{server}/api/v2.0/projects/{project_id}/repositories/{repo_id}/artifacts?page_size=0"
|
|
96
67
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
68
|
+
headers = {}
|
|
69
|
+
if auth:
|
|
70
|
+
headers["Authorization"] = auth
|
|
71
|
+
else:
|
|
72
|
+
# Retrieve token for authentication: https://github.com/goharbor/harbor/wiki/Harbor-FAQs#api
|
|
73
|
+
|
|
74
|
+
# Assume the token server is the same as the container image registry host if not explicitly set
|
|
75
|
+
token_server = token_server if token_server else server
|
|
76
|
+
token_url = f"https://{server}/service/token?scope=repository:{image_name}:pull"
|
|
77
|
+
resp = requests.get(token_url)
|
|
78
|
+
if resp.ok:
|
|
79
|
+
# Request to obtain token was successful, set Authorization header for registry API
|
|
80
|
+
token = resp.json().get('token')
|
|
81
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
82
|
+
resp = None
|
|
83
|
+
try:
|
|
84
|
+
resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
|
|
85
|
+
except requests.exceptions.SSLError:
|
|
86
|
+
# Connect to insecure registry over HTTP (development only)
|
|
87
|
+
if not verify:
|
|
88
|
+
url = f"http://{server}/api/v2.0/projects/{project_id}/repositories/{repo_id}/artifacts"
|
|
89
|
+
resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
|
|
110
90
|
|
|
111
|
-
|
|
112
|
-
|
|
91
|
+
if resp and resp.ok:
|
|
92
|
+
return [tag['name'] for image in resp.json() if image['tags'] for tag in image['tags']]
|
|
93
|
+
return []
|
|
113
94
|
|
|
114
95
|
|
|
115
96
|
REGISTRY_TYPE_MAPPING = {
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'harbor': HarborRegistry
|
|
97
|
+
'docker': DockerRegistry(),
|
|
98
|
+
'harbor': HarborRegistry()
|
|
119
99
|
}
|
|
120
100
|
|
|
121
101
|
|
|
122
|
-
def get_latest_tag_for_service(
|
|
102
|
+
def get_latest_tag_for_service(
|
|
103
|
+
service_config: ServiceConfig, system_config: SystemConfig, logger: Logger, prefix: str = ""):
|
|
123
104
|
def process_image(image):
|
|
124
105
|
# Find which server to search in
|
|
125
106
|
server = image.split("/")[0]
|
|
@@ -180,25 +161,23 @@ def get_latest_tag_for_service(service_config: ServiceConfig, system_config: Sys
|
|
|
180
161
|
# We're assuming that if only a password is given, then this is a token
|
|
181
162
|
auth = f"Bearer {service_config.docker_config.registry_password}"
|
|
182
163
|
|
|
164
|
+
registry = REGISTRY_TYPE_MAPPING[service_config.docker_config.registry_type]
|
|
165
|
+
token_server = None
|
|
183
166
|
proxies = None
|
|
184
167
|
for reg_conf in system_config.core.updater.registry_configs:
|
|
185
168
|
if reg_conf.name == server:
|
|
186
169
|
proxies = reg_conf.proxies or None
|
|
170
|
+
token_server = reg_conf.token_server or None
|
|
187
171
|
break
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
'update_channel': update_channel
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
registry: ContainerRegistry = REGISTRY_TYPE_MAPPING[registry_type](**registry_args)
|
|
199
|
-
tags = registry.get_image_tags(image_name)
|
|
200
|
-
|
|
173
|
+
if server == DEFAULT_DOCKER_REGISTRY:
|
|
174
|
+
tags = _get_dockerhub_tags(image_name, update_channel, proxies)
|
|
175
|
+
else:
|
|
176
|
+
tags = registry._get_proprietary_registry_tags(server, image_name, auth,
|
|
177
|
+
not system_config.services.allow_insecure_registry,
|
|
178
|
+
proxies, token_server)
|
|
201
179
|
tag_name = None
|
|
180
|
+
|
|
202
181
|
# Pre-filter tags to only consider 'compatible' tags relative to the running system
|
|
203
182
|
tags = [t for t in tags
|
|
204
183
|
if re.match(f"({FRAMEWORK_VERSION})[.]({SYSTEM_VERSION})[.]\\d+[.]({update_channel})\\d+", t)]
|
|
@@ -222,10 +201,28 @@ def get_latest_tag_for_service(service_config: ServiceConfig, system_config: Sys
|
|
|
222
201
|
image_variables = defaultdict(str)
|
|
223
202
|
image_variables.update(system_config.services.image_variables)
|
|
224
203
|
image = string.Template(image).safe_substitute(image_variables)
|
|
225
|
-
|
|
204
|
+
server, image_name = process_image(image)
|
|
226
205
|
|
|
227
206
|
# Append server to image if not the default server
|
|
228
|
-
if server !=
|
|
207
|
+
if server != "registry.hub.docker.com":
|
|
229
208
|
image_name = "/".join([server, image_name])
|
|
230
209
|
|
|
231
|
-
return image_name, tag_name, auth_config
|
|
210
|
+
return image_name, tag_name, auth_config
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# Default for obtaining tags from DockerHub
|
|
214
|
+
def _get_dockerhub_tags(image_name, update_channel, proxies=None):
|
|
215
|
+
# Find latest tag for each types
|
|
216
|
+
url = f"https://{DEFAULT_DOCKER_REGISTRY}/v2/repositories/{image_name}/tags" \
|
|
217
|
+
f"?page_size=5&page=1&name={update_channel}"
|
|
218
|
+
|
|
219
|
+
# Get tag list
|
|
220
|
+
resp = requests.get(url, proxies=proxies)
|
|
221
|
+
|
|
222
|
+
# Test for valid response
|
|
223
|
+
if resp.ok:
|
|
224
|
+
# Test for positive list of tags
|
|
225
|
+
resp_data = resp.json()
|
|
226
|
+
return [x['name'] for x in resp_data['results']]
|
|
227
|
+
|
|
228
|
+
return []
|
|
@@ -476,10 +476,10 @@ class ServiceUpdater(ThreadedCoreBase):
|
|
|
476
476
|
'version': tag,
|
|
477
477
|
'docker_config': {'image': install_data.get('image')}})
|
|
478
478
|
|
|
479
|
-
image_name, tag_name, auth
|
|
479
|
+
image_name, tag_name, auth = get_latest_tag_for_service(
|
|
480
480
|
service, self.config, self.log, prefix="[CI] ")
|
|
481
481
|
|
|
482
|
-
docker_config = dict(image=f"{image_name}:{tag_name}"
|
|
482
|
+
docker_config = dict(image=f"{image_name}:{tag_name}")
|
|
483
483
|
if auth:
|
|
484
484
|
docker_config.update(dict(registry_username=auth['username'],
|
|
485
485
|
registry_password=auth['password']))
|
|
@@ -675,7 +675,7 @@ class ServiceUpdater(ThreadedCoreBase):
|
|
|
675
675
|
|
|
676
676
|
for service in self.datastore.list_all_services(full=True):
|
|
677
677
|
discovered_services.append(service.name)
|
|
678
|
-
image_name, tag_name, auth
|
|
678
|
+
image_name, tag_name, auth = get_latest_tag_for_service(service, self.config, self.log, prefix="[CV] ")
|
|
679
679
|
self.latest_service_tags.set(service.name,
|
|
680
680
|
{'auth': auth, 'image': image_name, service.update_channel: tag_name})
|
|
681
681
|
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/worker.py
RENAMED
|
@@ -270,6 +270,7 @@ class FileProcessor(threading.Thread):
|
|
|
270
270
|
# Remove UI specific params
|
|
271
271
|
settings.pop('default_zip_password', None)
|
|
272
272
|
settings.pop('download_encoding', None)
|
|
273
|
+
settings.pop('executive_summary', None)
|
|
273
274
|
settings.pop('expand_min_score', None)
|
|
274
275
|
settings.pop('submission_view', None)
|
|
275
276
|
settings.pop('ui4', None)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
4.5.1.dev38
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/badlist_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/expiry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/__init__.py
RENAMED
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/replay.py
RENAMED
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/safelist_client.py
RENAMED
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/server_base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/tasking_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/__init__.py
RENAMED
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/crawler.py
RENAMED
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/safelist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_safelist_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_signature_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|