Skip to content

Commit

Permalink
v2.3.0 Release (#842)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradchiappetta authored Aug 1, 2024
1 parent 3a7eb7d commit abd3776
Show file tree
Hide file tree
Showing 15 changed files with 196 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.2.0
current_version = 2.3.0
tag = False
commit = False

Expand Down
19 changes: 18 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
Changelog
=========

Version `2.3.0`_
================
**Date**: July 30, 2024

* API client:

* Add `cve` command to query the CVE lookup API

* CLI:

* Add `cve` command to display result from CVE lookup API

* Dependencies:

* Updated cachetools to version 5.4.0

Version `2.2.0`_
================
**Date**: June 11, 2024
Expand Down Expand Up @@ -412,4 +428,5 @@ Version `0.2.0`_
.. _`2.0.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v1.3.0...2.0.0
.. _`2.0.1`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.0.0...2.0.1
.. _`2.1.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.0.1...2.1.0
.. _`2.2.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.1.0...HEAD
.. _`2.2.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.1.0...2.2.0
.. _`2.3.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.2.0...HEAD
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
author = "GreyNoise Intelligence"

# The full version, including alpha/beta/rc tags
release = "2.2.0"
release = "2.3.0"


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion requirements/common.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Click==8.1.7
ansimarkup==2.1.0
cachetools==5.3.3;python_version>='3'
cachetools==5.4.0;python_version>='3'
colorama==0.4.6
click-default-group==1.2.4
click-repl==0.3.0
Expand Down
6 changes: 3 additions & 3 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Requirements needed to develop the application
-r test.txt
advbumpversion==1.2.0
ipython==8.18.1;python_version>='3'
pre-commit==3.7.1
tox==4.15.1
ipython==8.26.0;python_version>='3'
pre-commit==3.8.0
tox==4.16.0
2 changes: 1 addition & 1 deletion requirements/docs.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Requirements needed to build the documentation
-r common.txt
Sphinx==7.3.7
Sphinx==8.0.0
sphinx-click==6.0.0
sphinx-rtd-theme==2.0.0
10 changes: 5 additions & 5 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ black<23.1.0;python_version<'3.7'
black==23.3.0;python_version=='3.7'
black==24.4.2;python_version>'3.7'
flake8<5.0.4;python_version<'3.8'
flake8==7.0.0;python_version>='3.8'
flake8==7.1.0;python_version>='3.8'
isort<5.12.0;python_version<'3.8'
isort==5.13.2;python_version>='3.8'
mock==5.1.0;python_version>='3.6'
pylint<2.16.2;python_version=='3.6' # pyup: ignore
pylint==2.17.7;python_version=='3.7'
pylint==3.2.3;python_version>='3.8'
pylint<2.17.8;python_version=='3.7'
pylint==3.2.6;python_version>='3.8'
pytest-cov==4.0.0;python_version=='3.6'
pytest-cov==4.1.0;python_version=='3.7'
pytest-cov==5.0.0;python_version>='3.8'
Expand All @@ -17,7 +17,7 @@ pytest==7.4.4;python_version=='3.7'
pytest==8.2.2;python_version>='3.8'
restructuredtext-lint==1.4.0
twine<4.0.2;python_version<='3.7'
twine==5.1.0;python_version>'3.7'
twine==5.1.1;python_version>'3.7'
yamllint==1.28.0;python_version=='3.6'
yamllint==1.32.0;python_version=='3.7'
yamllint==1.35.1;python_version>='3.8'
yamllint==1.35.1;python_version>='3.8'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def read(fname):

setup(
name="greynoise",
version="2.2.0",
version="2.3.0",
description="Abstraction to interact with GreyNoise API.",
url="https://greynoise.io/",
author="GreyNoise Intelligence",
Expand Down
2 changes: 1 addition & 1 deletion src/greynoise/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
__maintainer__ = "GreyNoise Intelligence"
__email__ = "hello@greynoise.io"
__status__ = "BETA"
__version__ = "2.2.0"
__version__ = "2.3.0"
25 changes: 25 additions & 0 deletions src/greynoise/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from greynoise.exceptions import RateLimitError, RequestFailure
from greynoise.util import (
load_config,
validate_cve_id,
validate_ip,
validate_similar_min_score,
validate_timeline_days,
Expand Down Expand Up @@ -61,6 +62,7 @@ class GreyNoise(object): # pylint: disable=R0205,R0902
EP_SENSOR_ACTIVITY = "v1/workspaces/{workspace_id}/sensors/activity"
EP_SENSOR_LIST = "v1/workspaces/{workspace_id}/sensors"
EP_PERSONA_DETAILS = "v1/personas/{persona_id}"
EP_CVE_LOOKUP = "v1/cve/{cve_id}"
EP_ANALYZE_UPLOAD = "v2/analyze/upload"
EP_ANALYZE = "v2/analyze/{id}"
EP_NOT_IMPLEMENTED = "v2/request/{subcommand}"
Expand Down Expand Up @@ -962,3 +964,26 @@ def persona_details(self, persona_id=None):
response = self._request(endpoint)

return response

def cve(self, cve_id=None):
"""Get CVE details by CVE ID
:param cve_id: ID of CVE
:type cve_id: str
"""
if self.offering == "community":
response = {
"message": "CVE lookup is not supported with Community offering"
}
else:
LOGGER.debug("Getting Details for CVE ID: %s...", cve_id)

# check if CVE submitted is in correct format
validate_cve_id(cve_id)

endpoint = self.EP_CVE_LOOKUP.format(cve_id=cve_id)
response = self._request(endpoint)

return response
40 changes: 38 additions & 2 deletions src/greynoise/cli/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def wrapper(api_client, *args, **kwargs):


def workspace_command(function):
"""Decorator that groups decorators common to sensor activity subcommands."""
"""Decorator that groups decorators common to workspace subcommands."""

@click.command()
@click.argument("workspace_id", required=True)
Expand Down Expand Up @@ -323,7 +323,7 @@ def wrapper(*args, **kwargs):


def persona_command(function):
"""Decorator that groups decorators common to sensor activity subcommands."""
"""Decorator that groups decorators common to persona subcommands."""

@click.command()
@click.argument("persona_id", required=True)
Expand Down Expand Up @@ -356,3 +356,39 @@ def wrapper(*args, **kwargs):
return function(*args, **kwargs)

return wrapper


def cve_command(function):
"""Decorator that groups decorators common to cve subcommand."""

@click.command()
@click.argument("cve_id", required=True)
@click.option("-k", "--api-key", help="Key to include in API requests")
@click.option(
"-O",
"--offering",
help="Which API offering to use, enterprise or community, "
"defaults to enterprise",
)
@click.option("-i", "--input", "input_file", type=click.File(), help="Input file")
@click.option(
"-o", "--output", "output_file", type=click.File(mode="w"), help="Output file"
)
@click.option(
"-f",
"--format",
"output_format",
type=click.Choice(["json", "txt", "xml"]),
default="txt",
help="Output format",
)
@click.option("-v", "--verbose", count=True, help="Verbose output")
@pass_api_client
@click.pass_context
@echo_result
@handle_exceptions
@functools.wraps(function)
def wrapper(*args, **kwargs):
return function(*args, **kwargs)

return wrapper
9 changes: 9 additions & 0 deletions src/greynoise/cli/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ def personadetails_formatter(results, verbose):
return template.render(results=results, verbose=verbose, max_width=max_width)


@colored_output
def cvedetails_formatter(results, verbose):
"""Convert CVE Details to human-readable text."""
template = JINJA2_ENV.get_template("cvedetails.txt.j2")
max_width, _ = shutil.get_terminal_size()
return template.render(results=results, verbose=verbose, max_width=max_width)


FORMATTERS = {
"json": json_formatter,
"xml": xml_formatter,
Expand All @@ -237,5 +245,6 @@ def personadetails_formatter(results, verbose):
"sensor-activity": sensoractivity_formatter,
"sensor-list": sensorlist_formatter,
"persona-details": personadetails_formatter,
"cve": cvedetails_formatter,
},
}
20 changes: 20 additions & 0 deletions src/greynoise/cli/subcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from greynoise.__version__ import __version__
from greynoise.cli.decorator import (
cve_command,
echo_result,
gnql_command,
handle_exceptions,
Expand Down Expand Up @@ -497,3 +498,22 @@ def persona_details(
persona_id=persona_id,
)
return result


@cve_command
def cve(
context,
api_client,
api_key,
input_file,
output_file,
output_format,
verbose,
cve_id,
offering,
):
"""Retrieve Details of a CVE."""
result = api_client.cve(
cve_id=cve_id,
)
return result
53 changes: 53 additions & 0 deletions src/greynoise/cli/templates/cvedetails.txt.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% import "macros.txt.j2" as macros with context %}
{%- if results.details %}
----------------------------
<header>CVE Details</header>
----------------------------
<key>CVE</key>: <value>{{ results.id }}</value>
<key>Vuln Name</key>: <value>{{ results.details.vulnerability_name }}</value>
<key>Vuln Description</key>: <value>{{ results.details.vulnerability_description }}</value>
<key>Vendor</key>: <value>{{ results.details.vendor }}</value>
<key>Product</key>: <value>{{ results.details.product }}</value>
<key>CVSS Score</key>: <value>{{ results.details.cve_cvss_score }}</value>
<key>Published to NIST NVD</key>: <value>{{ results.details.published_to_nist_nvd }}</value>

{% if results.timeline -%}
<header>Timeline Information</header>
--------------------
<key>Published Date</key>: <value>{{ results.timeline.cve_published_date.split("T")[0] }}</value>
<key>Last Updated Date</key>: <value>{{ results.timeline.cve_last_updated_date.split("T")[0] }}</value>
<key>First Known Date</key>: <value>{{ results.timeline.first_known_published_date.split("T")[0] }}</value>
<key>Added to CISA Kev Date</key>: <value>{{ results.timeline.cisa_kev_date_added.split("T")[0] }}</value>
{%- endif %}

{% if results.exploitation_details -%}
<header>Exploitation Details</header>
--------------------
<key>Attack Vector</key>: <value>{{ results.exploitation_details.attack_vector }}</value>
<key>Exploit Found</key>: <value>{{ results.exploitation_details.exploit_found }}</value>
<key>Exploit in KEV</key>: <value>{{ results.exploitation_details.exploitation_registered_in_kev }}</value>
<key>EPSS Score</key>: <value>{{ results.exploitation_details.epss_score }}</value>
{%- endif %}

{% if results.exploitation_stats -%}
<header>Exploitation Stats</header>
------------------
<key># of Exploits</key>: <value>{{ results.exploitation_stats.number_of_available_exploits }}</value>
<key># of Threat Actors</key>: <value>{{ results.exploitation_stats.number_of_threat_actors_exploiting_vulnerability }}</value>
<key># of Botnets </key>: <value>{{ results.exploitation_stats.number_of_botnets_exploiting_vulnerability }}</value>
{%- endif %}

{% if results.exploitation_activity -%}
<header>Exploitation Activity</header>
---------------------
<key>Activity Seen</key>: <value>{{ results.exploitation_activity.activity_seen }}</value>
<key># Benign Scanners - 1 Day</key>: <value>{{ results.exploitation_activity.benign_ip_count_1d }}</value>
<key># Benign Scanners - 10 Days</key>: <value>{{ results.exploitation_activity.benign_ip_count_10d }}</value>
<key># Benign Scanners - 30 Days</key>: <value>{{ results.exploitation_activity.benign_ip_count_30d }}</value>
<key># Suspicious Scanners - 1 Day</key>: <value>{{ results.exploitation_activity.threat_ip_count_1d }}</value>
<key># Suspicious Scanners - 10 Day</key>: <value>{{ results.exploitation_activity.threat_ip_count_10d }}</value>
<key># Suspicious Scanners - 30 Day</key>: <value>{{ results.exploitation_activity.threat_ip_count_30d }}</value>
{%- endif %}
{% else %}
Provided CVE was not found or valid.
{%- endif %}
19 changes: 19 additions & 0 deletions src/greynoise/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Utility functions."""
import logging
import os
import re
from ipaddress import IPv6Address, ip_address

from six.moves.configparser import ConfigParser
Expand Down Expand Up @@ -228,3 +229,21 @@ def validate_similar_min_score(min_score):
return True
else:
raise ValueError("Min Score must be a valid integer between 0 and 100.")


def validate_cve_id(cve_id):
"""Check if provided value is a valid CVE ID
:param cve_id: field value to validate.
:type cve_id: str
"""
# CVE regular expression
cve_pattern = r"CVE-\d{4}-\d{4,7}"

pattern = re.compile(cve_pattern)

if not pattern.match(cve_id):
raise ValueError("The provided ID does not match the format: CVE-XXXX-YYYYY")
else:
return True

0 comments on commit abd3776

Please sign in to comment.