greynoise 2.1.0__tar.gz → 2.3.0__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.
- {greynoise-2.1.0 → greynoise-2.3.0}/CHANGELOG.rst +48 -1
- {greynoise-2.1.0/src/greynoise.egg-info → greynoise-2.3.0}/PKG-INFO +49 -2
- {greynoise-2.1.0 → greynoise-2.3.0}/setup.py +2 -2
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/__version__.py +1 -1
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/api/__init__.py +253 -12
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/decorator.py +159 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/formatter.py +38 -2
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/subcommand.py +94 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/analyze.txt.j2 +29 -1
- greynoise-2.3.0/src/greynoise/cli/templates/cvedetails.txt.j2 +53 -0
- greynoise-2.3.0/src/greynoise/cli/templates/personadetails.txt.j2 +38 -0
- greynoise-2.3.0/src/greynoise/cli/templates/sensoractivity.txt.j2 +25 -0
- greynoise-2.3.0/src/greynoise/cli/templates/sensorlist.txt.j2 +30 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/util.py +19 -0
- {greynoise-2.1.0 → greynoise-2.3.0/src/greynoise.egg-info}/PKG-INFO +49 -2
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise.egg-info/SOURCES.txt +4 -1
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise.egg-info/requires.txt +1 -1
- {greynoise-2.1.0 → greynoise-2.3.0}/tests/test_api.py +122 -85
- greynoise-2.1.0/src/greynoise/api/analyzer.py +0 -158
- {greynoise-2.1.0 → greynoise-2.3.0}/LICENSE +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/MANIFEST.in +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/README.rst +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/setup.cfg +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/__init__.py +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/api/filter.py +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/__init__.py +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/helper.py +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/parameter.py +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/gnql_query.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/gnql_stats.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/interesting.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/ip_community.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/ip_context.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/ip_context_result.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/ip_multi_context.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/ip_quick_check.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/macros.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/riot.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/similarity.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/stats.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/timeline.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/cli/templates/timelinehourly.txt.j2 +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise/exceptions.py +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise.egg-info/dependency_links.txt +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise.egg-info/entry_points.txt +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise.egg-info/not-zip-safe +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/src/greynoise.egg-info/top_level.txt +0 -0
- {greynoise-2.1.0 → greynoise-2.3.0}/tests/test_util.py +0 -0
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version `2.3.0`_
|
|
6
|
+
================
|
|
7
|
+
**Date**: July 30, 2024
|
|
8
|
+
|
|
9
|
+
* API client:
|
|
10
|
+
|
|
11
|
+
* Add `cve` command to query the CVE lookup API
|
|
12
|
+
|
|
13
|
+
* CLI:
|
|
14
|
+
|
|
15
|
+
* Add `cve` command to display result from CVE lookup API
|
|
16
|
+
|
|
17
|
+
* Dependencies:
|
|
18
|
+
|
|
19
|
+
* Updated cachetools to version 5.4.0
|
|
20
|
+
|
|
21
|
+
Version `2.2.0`_
|
|
22
|
+
================
|
|
23
|
+
**Date**: June 11, 2024
|
|
24
|
+
|
|
25
|
+
* API client:
|
|
26
|
+
|
|
27
|
+
* Update `analyze` command to use analysis endpoint
|
|
28
|
+
* Add `persona_details` command to pull a sensor persona's details based on ID
|
|
29
|
+
* Add `sensor_list` command to pull list of sensors from a users workspace, based on API key used
|
|
30
|
+
* Add `sensor_activity` command to pull sensor activity feed from users workspace
|
|
31
|
+
* Add `sensor_activity_ips` command to pull list of source ips from sensor activity feed
|
|
32
|
+
|
|
33
|
+
* CLI:
|
|
34
|
+
|
|
35
|
+
* Update `analyze` output to match that of new endpoint response
|
|
36
|
+
* Add `persona-details` command to display command response
|
|
37
|
+
* Add `sensor-list` command to display command response
|
|
38
|
+
* Add `sensor-activity` command to display command response
|
|
39
|
+
|
|
40
|
+
* Dependencies:
|
|
41
|
+
|
|
42
|
+
* Updated Clic to version 8.1.7
|
|
43
|
+
* Updated ansimarkup to version 2.1.0
|
|
44
|
+
* Updated click-repl to version 0.3.0
|
|
45
|
+
* Updated dict2xml to version 1.7.5
|
|
46
|
+
* Updated jinja2 to version 3.1.4
|
|
47
|
+
* Updated more-itertools to version 10.3.0 (for py3.8 and above only)
|
|
48
|
+
* Updated requests to version 2.32.3
|
|
49
|
+
|
|
5
50
|
Version `2.1.0`_
|
|
6
51
|
================
|
|
7
52
|
**Date**: February 07, 2024
|
|
@@ -380,6 +425,8 @@ Version `0.2.0`_
|
|
|
380
425
|
.. _`1.2.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.1.0...1.2.0
|
|
381
426
|
.. _`1.2.1`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.2.0...1.2.1
|
|
382
427
|
.. _`1.3.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.2.1...1.3.0
|
|
383
|
-
.. _`2.0.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.3.0...
|
|
428
|
+
.. _`2.0.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.3.0...2.0.0
|
|
384
429
|
.. _`2.0.1`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.0.0...2.0.1
|
|
385
430
|
.. _`2.1.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.0.1...2.1.0
|
|
431
|
+
.. _`2.2.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.1.0...2.2.0
|
|
432
|
+
.. _`2.3.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.2.0...HEAD
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: greynoise
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Abstraction to interact with GreyNoise API.
|
|
5
5
|
Home-page: https://greynoise.io/
|
|
6
6
|
Download-URL: https://github.com/GreyNoise-Intelligence/pygreynoise
|
|
@@ -124,6 +124,51 @@ Usage
|
|
|
124
124
|
Changelog
|
|
125
125
|
=========
|
|
126
126
|
|
|
127
|
+
Version `2.3.0`_
|
|
128
|
+
================
|
|
129
|
+
**Date**: July 30, 2024
|
|
130
|
+
|
|
131
|
+
* API client:
|
|
132
|
+
|
|
133
|
+
* Add `cve` command to query the CVE lookup API
|
|
134
|
+
|
|
135
|
+
* CLI:
|
|
136
|
+
|
|
137
|
+
* Add `cve` command to display result from CVE lookup API
|
|
138
|
+
|
|
139
|
+
* Dependencies:
|
|
140
|
+
|
|
141
|
+
* Updated cachetools to version 5.4.0
|
|
142
|
+
|
|
143
|
+
Version `2.2.0`_
|
|
144
|
+
================
|
|
145
|
+
**Date**: June 11, 2024
|
|
146
|
+
|
|
147
|
+
* API client:
|
|
148
|
+
|
|
149
|
+
* Update `analyze` command to use analysis endpoint
|
|
150
|
+
* Add `persona_details` command to pull a sensor persona's details based on ID
|
|
151
|
+
* Add `sensor_list` command to pull list of sensors from a users workspace, based on API key used
|
|
152
|
+
* Add `sensor_activity` command to pull sensor activity feed from users workspace
|
|
153
|
+
* Add `sensor_activity_ips` command to pull list of source ips from sensor activity feed
|
|
154
|
+
|
|
155
|
+
* CLI:
|
|
156
|
+
|
|
157
|
+
* Update `analyze` output to match that of new endpoint response
|
|
158
|
+
* Add `persona-details` command to display command response
|
|
159
|
+
* Add `sensor-list` command to display command response
|
|
160
|
+
* Add `sensor-activity` command to display command response
|
|
161
|
+
|
|
162
|
+
* Dependencies:
|
|
163
|
+
|
|
164
|
+
* Updated Clic to version 8.1.7
|
|
165
|
+
* Updated ansimarkup to version 2.1.0
|
|
166
|
+
* Updated click-repl to version 0.3.0
|
|
167
|
+
* Updated dict2xml to version 1.7.5
|
|
168
|
+
* Updated jinja2 to version 3.1.4
|
|
169
|
+
* Updated more-itertools to version 10.3.0 (for py3.8 and above only)
|
|
170
|
+
* Updated requests to version 2.32.3
|
|
171
|
+
|
|
127
172
|
Version `2.1.0`_
|
|
128
173
|
================
|
|
129
174
|
**Date**: February 07, 2024
|
|
@@ -502,6 +547,8 @@ Version `0.2.0`_
|
|
|
502
547
|
.. _`1.2.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.1.0...1.2.0
|
|
503
548
|
.. _`1.2.1`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.2.0...1.2.1
|
|
504
549
|
.. _`1.3.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.2.1...1.3.0
|
|
505
|
-
.. _`2.0.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.3.0...
|
|
550
|
+
.. _`2.0.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.3.0...2.0.0
|
|
506
551
|
.. _`2.0.1`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.0.0...2.0.1
|
|
507
552
|
.. _`2.1.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.0.1...2.1.0
|
|
553
|
+
.. _`2.2.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.1.0...2.2.0
|
|
554
|
+
.. _`2.3.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.2.0...HEAD
|
|
@@ -20,14 +20,14 @@ INSTALL_REQUIRES = [
|
|
|
20
20
|
"click-repl",
|
|
21
21
|
"dict2xml",
|
|
22
22
|
"jinja2",
|
|
23
|
-
"more-itertools",
|
|
23
|
+
"more-itertools>=8.14.0",
|
|
24
24
|
"requests",
|
|
25
25
|
"six",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
setup(
|
|
29
29
|
name="greynoise",
|
|
30
|
-
version="2.
|
|
30
|
+
version="2.3.0",
|
|
31
31
|
description="Abstraction to interact with GreyNoise API.",
|
|
32
32
|
url="https://greynoise.io/",
|
|
33
33
|
author="GreyNoise Intelligence",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
5
7
|
from collections import OrderedDict
|
|
6
8
|
|
|
7
9
|
import cachetools
|
|
@@ -9,11 +11,11 @@ import more_itertools
|
|
|
9
11
|
import requests
|
|
10
12
|
|
|
11
13
|
from greynoise.__version__ import __version__
|
|
12
|
-
from greynoise.api.analyzer import Analyzer
|
|
13
14
|
from greynoise.api.filter import Filter
|
|
14
15
|
from greynoise.exceptions import RateLimitError, RequestFailure
|
|
15
16
|
from greynoise.util import (
|
|
16
17
|
load_config,
|
|
18
|
+
validate_cve_id,
|
|
17
19
|
validate_ip,
|
|
18
20
|
validate_similar_min_score,
|
|
19
21
|
validate_timeline_days,
|
|
@@ -31,7 +33,6 @@ def initialize_cache(cache_max_size, cache_ttl):
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
34
|
-
|
|
35
36
|
"""GreyNoise API client.
|
|
36
37
|
|
|
37
38
|
:param api_key: Key use to access the API.
|
|
@@ -58,6 +59,12 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
58
59
|
EP_META_METADATA = "v2/meta/metadata"
|
|
59
60
|
EP_PING = "ping"
|
|
60
61
|
EP_RIOT = "v2/riot/{ip_address}"
|
|
62
|
+
EP_SENSOR_ACTIVITY = "v1/workspaces/{workspace_id}/sensors/activity"
|
|
63
|
+
EP_SENSOR_LIST = "v1/workspaces/{workspace_id}/sensors"
|
|
64
|
+
EP_PERSONA_DETAILS = "v1/personas/{persona_id}"
|
|
65
|
+
EP_CVE_LOOKUP = "v1/cve/{cve_id}"
|
|
66
|
+
EP_ANALYZE_UPLOAD = "v2/analyze/upload"
|
|
67
|
+
EP_ANALYZE = "v2/analyze/{id}"
|
|
61
68
|
EP_NOT_IMPLEMENTED = "v2/request/{subcommand}"
|
|
62
69
|
UNKNOWN_CODE_MESSAGE = "Code message unknown: {}"
|
|
63
70
|
CODE_MESSAGES = {
|
|
@@ -143,7 +150,15 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
143
150
|
self.ip_quick_check_cache = initialize_cache(cache_max_size, cache_ttl)
|
|
144
151
|
self.ip_context_cache = initialize_cache(cache_max_size, cache_ttl)
|
|
145
152
|
|
|
146
|
-
def _request(
|
|
153
|
+
def _request(
|
|
154
|
+
self,
|
|
155
|
+
endpoint,
|
|
156
|
+
params=None,
|
|
157
|
+
json=None,
|
|
158
|
+
files=None,
|
|
159
|
+
method="get",
|
|
160
|
+
include_headers=False,
|
|
161
|
+
):
|
|
147
162
|
"""Handle the requesting of information from the API.
|
|
148
163
|
|
|
149
164
|
:param endpoint: Endpoint to send the request to
|
|
@@ -152,6 +167,8 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
152
167
|
:type param: dict
|
|
153
168
|
:param json: Request's JSON payload
|
|
154
169
|
:type json: dict
|
|
170
|
+
:param files: Request's file for update
|
|
171
|
+
:type files: text file
|
|
155
172
|
:param method: Request method name
|
|
156
173
|
:type method: str
|
|
157
174
|
:returns: Response's JSON payload
|
|
@@ -179,6 +196,7 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
179
196
|
LOGGER.debug("Sending API request...headers: %s", headers)
|
|
180
197
|
LOGGER.debug("Sending API request...params: %s", params)
|
|
181
198
|
LOGGER.debug("Sending API request...json: %s", json)
|
|
199
|
+
LOGGER.debug("Sending API request...files: %s", files)
|
|
182
200
|
LOGGER.debug("Sending API request...proxy: %s", self.proxy)
|
|
183
201
|
|
|
184
202
|
request_method = getattr(self.session, method)
|
|
@@ -190,13 +208,20 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
190
208
|
timeout=self.timeout,
|
|
191
209
|
params=params,
|
|
192
210
|
json=json,
|
|
211
|
+
files=files,
|
|
193
212
|
proxies=proxies,
|
|
194
213
|
)
|
|
195
214
|
else:
|
|
196
215
|
response = request_method(
|
|
197
|
-
url,
|
|
216
|
+
url,
|
|
217
|
+
headers=headers,
|
|
218
|
+
timeout=self.timeout,
|
|
219
|
+
params=params,
|
|
220
|
+
json=json,
|
|
221
|
+
files=files,
|
|
198
222
|
)
|
|
199
223
|
content_type = response.headers.get("Content-Type", "")
|
|
224
|
+
headers = response.headers
|
|
200
225
|
if "application/json" in content_type:
|
|
201
226
|
body = response.json()
|
|
202
227
|
else:
|
|
@@ -208,8 +233,10 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
208
233
|
raise RateLimitError()
|
|
209
234
|
if response.status_code >= 400 and response.status_code != 404:
|
|
210
235
|
raise RequestFailure(response.status_code, body)
|
|
211
|
-
|
|
212
|
-
|
|
236
|
+
if include_headers:
|
|
237
|
+
return body, headers
|
|
238
|
+
else:
|
|
239
|
+
return body
|
|
213
240
|
|
|
214
241
|
def analyze(self, text):
|
|
215
242
|
"""Aggregate stats related to IP addresses from a given text.
|
|
@@ -221,14 +248,48 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
221
248
|
|
|
222
249
|
"""
|
|
223
250
|
if self.offering == "community":
|
|
224
|
-
|
|
225
|
-
{"message": "Quick Lookup not supported with Community offering"}
|
|
226
|
-
]
|
|
251
|
+
text_stats = [{"message": "Analyze not supported with Community offering"}]
|
|
227
252
|
else:
|
|
228
|
-
|
|
229
|
-
|
|
253
|
+
text_stats = {
|
|
254
|
+
"query": [],
|
|
255
|
+
"count": 0,
|
|
256
|
+
"stats": {},
|
|
257
|
+
}
|
|
230
258
|
|
|
231
|
-
|
|
259
|
+
files = {"file": text}
|
|
260
|
+
upload = self._request(self.EP_ANALYZE_UPLOAD, files=files, method="post")
|
|
261
|
+
|
|
262
|
+
if "uuid" in upload:
|
|
263
|
+
uuid = upload["uuid"]
|
|
264
|
+
state = upload["state"]
|
|
265
|
+
while state != "completed":
|
|
266
|
+
url = self.EP_ANALYZE.format(id=uuid)
|
|
267
|
+
response = self._request(url)
|
|
268
|
+
state = response["state"]
|
|
269
|
+
time.sleep(5)
|
|
270
|
+
unique_ip_list = (
|
|
271
|
+
response["details"].get("noise_ips_found", [])
|
|
272
|
+
+ response["details"].get("unknown_ips", [])
|
|
273
|
+
+ response["details"].get("riot_ips_found", [])
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
text_stats["summary"] = {
|
|
277
|
+
"ip_count": response["details"].get("unique_ips", 0),
|
|
278
|
+
"noise_ip_count": response["details"].get("noise_ips", 0),
|
|
279
|
+
"not_noise_ip_count": response["details"].get("non_noise_ips", 0),
|
|
280
|
+
"riot_ip_count": response["details"].get("riot_ips", 0),
|
|
281
|
+
"noise_ip_ratio": response["details"].get(
|
|
282
|
+
"percentage_of_noise_ips", 0
|
|
283
|
+
),
|
|
284
|
+
"riot_ip_ratio": response["details"].get(
|
|
285
|
+
"percentage_of_riot_ips", 0
|
|
286
|
+
),
|
|
287
|
+
}
|
|
288
|
+
text_stats["stats"] = response.get("stats")
|
|
289
|
+
text_stats["query"] = unique_ip_list
|
|
290
|
+
text_stats["count"] = response["details"].get("unique_ips", 0)
|
|
291
|
+
|
|
292
|
+
return text_stats
|
|
232
293
|
|
|
233
294
|
def filter(self, text, noise_only=False, riot_only=False):
|
|
234
295
|
"""Filter lines that contain IP addresses from a given text.
|
|
@@ -580,6 +641,114 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
580
641
|
|
|
581
642
|
return response
|
|
582
643
|
|
|
644
|
+
def sensor_activity(
|
|
645
|
+
self,
|
|
646
|
+
workspace_id,
|
|
647
|
+
file_format=None,
|
|
648
|
+
start_time=None,
|
|
649
|
+
end_time=None,
|
|
650
|
+
persona_id=None,
|
|
651
|
+
source_ip=None,
|
|
652
|
+
size=None,
|
|
653
|
+
scroll=None,
|
|
654
|
+
include_headers=False,
|
|
655
|
+
):
|
|
656
|
+
"""Get session data from sensors"""
|
|
657
|
+
LOGGER.debug(
|
|
658
|
+
"Running Sensor Activity: %s %s %s %s %s %s %s %s...",
|
|
659
|
+
workspace_id,
|
|
660
|
+
file_format,
|
|
661
|
+
start_time,
|
|
662
|
+
end_time,
|
|
663
|
+
persona_id,
|
|
664
|
+
source_ip,
|
|
665
|
+
size,
|
|
666
|
+
scroll,
|
|
667
|
+
)
|
|
668
|
+
if file_format is None or file_format == "json":
|
|
669
|
+
params = {"format": "json"}
|
|
670
|
+
elif file_format == "csv":
|
|
671
|
+
params = {"format": file_format}
|
|
672
|
+
else:
|
|
673
|
+
LOGGER.error(
|
|
674
|
+
f"Value for file_format is not valid (valid: json, csv): {file_format}"
|
|
675
|
+
)
|
|
676
|
+
sys.exit(1)
|
|
677
|
+
|
|
678
|
+
if start_time is not None:
|
|
679
|
+
params["start_time"] = start_time
|
|
680
|
+
if end_time is not None:
|
|
681
|
+
params["end_time"] = end_time
|
|
682
|
+
if persona_id is not None:
|
|
683
|
+
params["persona_id"] = persona_id
|
|
684
|
+
if source_ip is not None:
|
|
685
|
+
params["source_ip"] = source_ip
|
|
686
|
+
if size is not None:
|
|
687
|
+
params["size"] = size
|
|
688
|
+
if scroll is not None:
|
|
689
|
+
params["scroll"] = scroll
|
|
690
|
+
endpoint = self.EP_SENSOR_ACTIVITY.format(workspace_id=workspace_id)
|
|
691
|
+
response, headers = self._request(endpoint, params=params, include_headers=True)
|
|
692
|
+
|
|
693
|
+
if include_headers:
|
|
694
|
+
return response, headers
|
|
695
|
+
else:
|
|
696
|
+
return response
|
|
697
|
+
|
|
698
|
+
def sensor_activity_ips(
|
|
699
|
+
self,
|
|
700
|
+
workspace_id,
|
|
701
|
+
file_format=None,
|
|
702
|
+
start_time=None,
|
|
703
|
+
end_time=None,
|
|
704
|
+
persona_id=None,
|
|
705
|
+
source_ip=None,
|
|
706
|
+
size=None,
|
|
707
|
+
scroll=None,
|
|
708
|
+
):
|
|
709
|
+
"""Get session data from sensors"""
|
|
710
|
+
LOGGER.debug(
|
|
711
|
+
"Running Sensor Activity: %s %s %s %s %s %s %s %s...",
|
|
712
|
+
workspace_id,
|
|
713
|
+
file_format,
|
|
714
|
+
start_time,
|
|
715
|
+
end_time,
|
|
716
|
+
persona_id,
|
|
717
|
+
source_ip,
|
|
718
|
+
size,
|
|
719
|
+
scroll,
|
|
720
|
+
)
|
|
721
|
+
if file_format is None or file_format == "json":
|
|
722
|
+
params = {"format": "json"}
|
|
723
|
+
elif file_format == "csv":
|
|
724
|
+
params = {"format": file_format}
|
|
725
|
+
else:
|
|
726
|
+
LOGGER.error(
|
|
727
|
+
f"Value for file_format is not valid (valid: json, csv): {file_format}"
|
|
728
|
+
)
|
|
729
|
+
sys.exit(1)
|
|
730
|
+
|
|
731
|
+
if start_time is not None:
|
|
732
|
+
params["start_time"] = start_time
|
|
733
|
+
if end_time is not None:
|
|
734
|
+
params["end_time"] = end_time
|
|
735
|
+
if persona_id is not None:
|
|
736
|
+
params["persona_id"] = persona_id
|
|
737
|
+
if source_ip is not None:
|
|
738
|
+
params["source_ip"] = source_ip
|
|
739
|
+
if size is not None:
|
|
740
|
+
params["size"] = size
|
|
741
|
+
if scroll is not None:
|
|
742
|
+
params["scroll"] = scroll
|
|
743
|
+
endpoint = self.EP_SENSOR_ACTIVITY.format(workspace_id=workspace_id)
|
|
744
|
+
response = self._request(endpoint, params=params)
|
|
745
|
+
ip_list = []
|
|
746
|
+
for item in response:
|
|
747
|
+
ip_list.append(item.get("source_ip", ""))
|
|
748
|
+
final_ip_list = list(set(ip_list))
|
|
749
|
+
|
|
750
|
+
return final_ip_list
|
|
751
|
+
|
|
583
752
|
def similar(self, ip_address, limit=None, min_score=None):
|
|
584
753
|
"""Query IP on the IP Similarity API
|
|
585
754
|
|
|
@@ -746,3 +915,75 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
|
|
|
746
915
|
response["ip"] = ip_address
|
|
747
916
|
|
|
748
917
|
return response
|
|
918
|
+
|
|
919
|
+
def sensor_list(self, workspace_id=None):
|
|
920
|
+
"""Get list of current sensors for Workspace
|
|
921
|
+
|
|
922
|
+
:param workspace_id: ID of Workspace
|
|
923
|
+
:type workspace_id: str
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
"""
|
|
927
|
+
if self.offering == "community":
|
|
928
|
+
response = {
|
|
929
|
+
"message": "Sensors List is not supported with Community offering"
|
|
930
|
+
}
|
|
931
|
+
else:
|
|
932
|
+
LOGGER.debug("Getting Sensor List for Workspace ID: %s...", workspace_id)
|
|
933
|
+
|
|
934
|
+
endpoint = self.EP_SENSOR_LIST.format(workspace_id=workspace_id)
|
|
935
|
+
response = self._request(endpoint)
|
|
936
|
+
new_response = {}
|
|
937
|
+
if "items" in response:
|
|
938
|
+
new_response["items"] = []
|
|
939
|
+
for sensor in response["items"]:
|
|
940
|
+
persona = self.persona_details(sensor["persona"])
|
|
941
|
+
sensor["persona_name"] = persona.get("name", "")
|
|
942
|
+
new_response["items"].append(sensor)
|
|
943
|
+
else:
|
|
944
|
+
new_response = response
|
|
945
|
+
|
|
946
|
+
return new_response
|
|
947
|
+
|
|
948
|
+
def persona_details(self, persona_id=None):
|
|
949
|
+
"""Get persona details by ID
|
|
950
|
+
|
|
951
|
+
:param persona_id: ID of Persona
|
|
952
|
+
:type persona_id: str
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
"""
|
|
956
|
+
if self.offering == "community":
|
|
957
|
+
response = {
|
|
958
|
+
"message": "Persona Details is not supported with Community offering"
|
|
959
|
+
}
|
|
960
|
+
else:
|
|
961
|
+
LOGGER.debug("Getting Persona Details for Workspace ID: %s...", persona_id)
|
|
962
|
+
|
|
963
|
+
endpoint = self.EP_PERSONA_DETAILS.format(persona_id=persona_id)
|
|
964
|
+
response = self._request(endpoint)
|
|
965
|
+
|
|
966
|
+
return response
|
|
967
|
+
|
|
968
|
+
def cve(self, cve_id=None):
|
|
969
|
+
"""Get CVE details by CVE ID
|
|
970
|
+
|
|
971
|
+
:param cve_id: ID of CVE
|
|
972
|
+
:type cve_id: str
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
"""
|
|
976
|
+
if self.offering == "community":
|
|
977
|
+
response = {
|
|
978
|
+
"message": "CVE lookup is not supported with Community offering"
|
|
979
|
+
}
|
|
980
|
+
else:
|
|
981
|
+
LOGGER.debug("Getting Details for CVE ID: %s...", cve_id)
|
|
982
|
+
|
|
983
|
+
# check if CVE submitted is in correct format
|
|
984
|
+
validate_cve_id(cve_id)
|
|
985
|
+
|
|
986
|
+
endpoint = self.EP_CVE_LOOKUP.format(cve_id=cve_id)
|
|
987
|
+
response = self._request(endpoint)
|
|
988
|
+
|
|
989
|
+
return response
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Decorators used to add common functionality to subcommands.
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import functools
|
|
7
8
|
import logging
|
|
8
9
|
|
|
@@ -62,11 +63,14 @@ def handle_exceptions(function):
|
|
|
62
63
|
try:
|
|
63
64
|
return function(*args, **kwargs)
|
|
64
65
|
except RequestFailure as exception:
|
|
66
|
+
status = exception.args[0]
|
|
65
67
|
body = exception.args[1]
|
|
66
68
|
if "message" in body:
|
|
67
69
|
error_message = "API error: {}".format(body["message"])
|
|
68
70
|
elif "error" in body:
|
|
69
71
|
error_message = "API error: {}".format(body["error"])
|
|
72
|
+
elif not body:
|
|
73
|
+
error_message = "API error: {}".format(status)
|
|
70
74
|
else:
|
|
71
75
|
error_message = "API error: {}".format(body)
|
|
72
76
|
LOGGER.error(error_message)
|
|
@@ -233,3 +237,158 @@ def not_implemented_command(function):
|
|
|
233
237
|
raise SubcommandNotImplemented(command_name)
|
|
234
238
|
|
|
235
239
|
return wrapper
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def workspace_command(function):
|
|
243
|
+
"""Decorator that groups decorators common to workspace subcommands."""
|
|
244
|
+
|
|
245
|
+
@click.command()
|
|
246
|
+
@click.argument("workspace_id", required=True)
|
|
247
|
+
@click.option("-k", "--api-key", help="Key to include in API requests")
|
|
248
|
+
@click.option(
|
|
249
|
+
"-O",
|
|
250
|
+
"--offering",
|
|
251
|
+
help="Which API offering to use, enterprise or community, "
|
|
252
|
+
"defaults to enterprise",
|
|
253
|
+
)
|
|
254
|
+
@click.option("-i", "--input", "input_file", type=click.File(), help="Input file")
|
|
255
|
+
@click.option(
|
|
256
|
+
"-o", "--output", "output_file", type=click.File(mode="w"), help="Output file"
|
|
257
|
+
)
|
|
258
|
+
@click.option(
|
|
259
|
+
"-f",
|
|
260
|
+
"--format",
|
|
261
|
+
"output_format",
|
|
262
|
+
type=click.Choice(["json", "txt", "xml"]),
|
|
263
|
+
default="txt",
|
|
264
|
+
help="Output format",
|
|
265
|
+
)
|
|
266
|
+
@click.option("-v", "--verbose", count=True, help="Verbose output")
|
|
267
|
+
@pass_api_client
|
|
268
|
+
@click.pass_context
|
|
269
|
+
@echo_result
|
|
270
|
+
@handle_exceptions
|
|
271
|
+
@functools.wraps(function)
|
|
272
|
+
def wrapper(*args, **kwargs):
|
|
273
|
+
return function(*args, **kwargs)
|
|
274
|
+
|
|
275
|
+
return wrapper
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def sensor_activity_command(function):
|
|
279
|
+
"""Decorator that groups decorators common to sensor activity subcommands."""
|
|
280
|
+
|
|
281
|
+
@click.command()
|
|
282
|
+
@click.argument("workspace_id", required=True)
|
|
283
|
+
@click.option(
|
|
284
|
+
"--start_time", "start_time", help="Earliest session start time to return"
|
|
285
|
+
)
|
|
286
|
+
@click.option("--end_time", "end_time", help="Latest session start time to return")
|
|
287
|
+
@click.option(
|
|
288
|
+
"--file_format", "file_format", help="Format for output file", default="json"
|
|
289
|
+
)
|
|
290
|
+
@click.option("--persona_id", "persona_id", help="Id for the desired persona")
|
|
291
|
+
@click.option("--source_ip", "source_ip", help="Ip for the desired source")
|
|
292
|
+
@click.option("--size", "size", help="Max number of results to return")
|
|
293
|
+
@click.option("--scroll", "scroll", help="Scroll token for pagination")
|
|
294
|
+
@click.option("-k", "--api-key", help="Key to include in API requests")
|
|
295
|
+
@click.option(
|
|
296
|
+
"-O",
|
|
297
|
+
"--offering",
|
|
298
|
+
help="Which API offering to use, enterprise or community, "
|
|
299
|
+
"defaults to enterprise",
|
|
300
|
+
)
|
|
301
|
+
@click.option("-i", "--input", "input_file", type=click.File(), help="Input file")
|
|
302
|
+
@click.option(
|
|
303
|
+
"-o", "--output", "output_file", type=click.File(mode="w"), help="Output file"
|
|
304
|
+
)
|
|
305
|
+
@click.option(
|
|
306
|
+
"-f",
|
|
307
|
+
"--format",
|
|
308
|
+
"output_format",
|
|
309
|
+
type=click.Choice(["json", "txt", "xml"]),
|
|
310
|
+
default="txt",
|
|
311
|
+
help="Output format",
|
|
312
|
+
)
|
|
313
|
+
@click.option("-v", "--verbose", count=True, help="Verbose output")
|
|
314
|
+
@pass_api_client
|
|
315
|
+
@click.pass_context
|
|
316
|
+
@echo_result
|
|
317
|
+
@handle_exceptions
|
|
318
|
+
@functools.wraps(function)
|
|
319
|
+
def wrapper(*args, **kwargs):
|
|
320
|
+
return function(*args, **kwargs)
|
|
321
|
+
|
|
322
|
+
return wrapper
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def persona_command(function):
|
|
326
|
+
"""Decorator that groups decorators common to persona subcommands."""
|
|
327
|
+
|
|
328
|
+
@click.command()
|
|
329
|
+
@click.argument("persona_id", required=True)
|
|
330
|
+
@click.option("-k", "--api-key", help="Key to include in API requests")
|
|
331
|
+
@click.option(
|
|
332
|
+
"-O",
|
|
333
|
+
"--offering",
|
|
334
|
+
help="Which API offering to use, enterprise or community, "
|
|
335
|
+
"defaults to enterprise",
|
|
336
|
+
)
|
|
337
|
+
@click.option("-i", "--input", "input_file", type=click.File(), help="Input file")
|
|
338
|
+
@click.option(
|
|
339
|
+
"-o", "--output", "output_file", type=click.File(mode="w"), help="Output file"
|
|
340
|
+
)
|
|
341
|
+
@click.option(
|
|
342
|
+
"-f",
|
|
343
|
+
"--format",
|
|
344
|
+
"output_format",
|
|
345
|
+
type=click.Choice(["json", "txt", "xml"]),
|
|
346
|
+
default="txt",
|
|
347
|
+
help="Output format",
|
|
348
|
+
)
|
|
349
|
+
@click.option("-v", "--verbose", count=True, help="Verbose output")
|
|
350
|
+
@pass_api_client
|
|
351
|
+
@click.pass_context
|
|
352
|
+
@echo_result
|
|
353
|
+
@handle_exceptions
|
|
354
|
+
@functools.wraps(function)
|
|
355
|
+
def wrapper(*args, **kwargs):
|
|
356
|
+
return function(*args, **kwargs)
|
|
357
|
+
|
|
358
|
+
return wrapper
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def cve_command(function):
|
|
362
|
+
"""Decorator that groups decorators common to cve subcommand."""
|
|
363
|
+
|
|
364
|
+
@click.command()
|
|
365
|
+
@click.argument("cve_id", required=True)
|
|
366
|
+
@click.option("-k", "--api-key", help="Key to include in API requests")
|
|
367
|
+
@click.option(
|
|
368
|
+
"-O",
|
|
369
|
+
"--offering",
|
|
370
|
+
help="Which API offering to use, enterprise or community, "
|
|
371
|
+
"defaults to enterprise",
|
|
372
|
+
)
|
|
373
|
+
@click.option("-i", "--input", "input_file", type=click.File(), help="Input file")
|
|
374
|
+
@click.option(
|
|
375
|
+
"-o", "--output", "output_file", type=click.File(mode="w"), help="Output file"
|
|
376
|
+
)
|
|
377
|
+
@click.option(
|
|
378
|
+
"-f",
|
|
379
|
+
"--format",
|
|
380
|
+
"output_format",
|
|
381
|
+
type=click.Choice(["json", "txt", "xml"]),
|
|
382
|
+
default="txt",
|
|
383
|
+
help="Output format",
|
|
384
|
+
)
|
|
385
|
+
@click.option("-v", "--verbose", count=True, help="Verbose output")
|
|
386
|
+
@pass_api_client
|
|
387
|
+
@click.pass_context
|
|
388
|
+
@echo_result
|
|
389
|
+
@handle_exceptions
|
|
390
|
+
@functools.wraps(function)
|
|
391
|
+
def wrapper(*args, **kwargs):
|
|
392
|
+
return function(*args, **kwargs)
|
|
393
|
+
|
|
394
|
+
return wrapper
|