Skip to content

Commit

Permalink
feat(cve_handler): filter by inventory groups
Browse files Browse the repository at this point in the history
  • Loading branch information
radohanculak authored and jdobes committed Jul 31, 2023
1 parent 0ece58b commit c1a27f7
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 0 deletions.
53 changes: 53 additions & 0 deletions manager.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ paths:
- $ref: '#/components/parameters/report'
- $ref: '#/components/parameters/ansible'
- $ref: '#/components/parameters/mssql'
- $ref: '#/components/parameters/group_name'
- $ref: '#/components/parameters/group_id'
- $ref: '#/components/parameters/ungrouped_hosts'

/cves/{cve_id}/affected_systems/ids:
get:
Expand Down Expand Up @@ -174,6 +177,9 @@ paths:
- $ref: '#/components/parameters/remediation'
- $ref: '#/components/parameters/ansible'
- $ref: '#/components/parameters/mssql'
- $ref: '#/components/parameters/group_name'
- $ref: '#/components/parameters/group_id'
- $ref: '#/components/parameters/ungrouped_hosts'

/cves/business_risk:
patch:
Expand Down Expand Up @@ -1176,6 +1182,30 @@ components:
type: boolean
example: false

group_name:
in: query
name: group_name
description: Name of the inventory group.
schema:
type: string
example: 'Production'

group_id:
in: query
name: group_id
description: ID of the inventory group.
schema:
type: string
example: '00000000-1111-0000-0000-000000000000'

ungrouped_hosts:
in: query
name: ungrouped_hosts
description: Boolean values which tells whether to include ungrouped hosts.
schema:
type: boolean
example: false

securitySchemes:
BasicAuth:
type: http
Expand Down Expand Up @@ -1440,6 +1470,21 @@ components:
type: boolean
description: CVEs without Errata feature flag
nullable: false
group_name:
type: string
description: Name of the inventory group.
example: 'Production'
nullable: true
group_id:
type: string
description: ID of the inventory group.
example: '00000000-1111-0000-0000-000000000000'
nullable: true
ungrouped_hosts:
type: boolean
description: Boolean values which tells whether to include ungrouped hosts.
example: false
nullable: true
required:
- status_id
- rule_key
Expand All @@ -1449,6 +1494,9 @@ components:
- first_reported_from
- first_reported_to
- cves_without_errata
- group_name
- group_id
- ungrouped_hosts

MetaSystems:
allOf:
Expand Down Expand Up @@ -1762,6 +1810,10 @@ components:
description: Reason why the system is not vulnerable.
example: SELinux mitigates the issue
nullable: true
inventory_group:
type: array
description: JSON array of name and ID of inventory group.
example: "[{'id': '00000000-1111-0000-0000-000000000000', 'name': 'group01'}]"
required:
- cve_status_id
- culled_timestamp
Expand All @@ -1784,6 +1836,7 @@ components:
- advisory_available
- remediation
- mitigation_reason
- inventory_group
required:
- id
- type
Expand Down
12 changes: 12 additions & 0 deletions manager/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,18 @@ def inventory_groups_condition(group_ids) -> Tuple[str, bool]:
return "{" + ",".join(res) + "}", include_ungrouped


def transform_names(names):
"""Transforms comma-separated names to format suitable for SQL clause."""
res, _ = inventory_groups_condition([[{"name": name}] for name in names.split(",")])
return res


def transform_ids(ids):
"""Transforms comma-separated ids to format suitable for SQL clause."""
res, _ = inventory_groups_condition([[{"id": id}] for id in ids.split(",")])
return res


def cyndi_join(query):
"""Function adds join on cyndi table, based if cyndi is enabled."""
query = query.join(
Expand Down
10 changes: 10 additions & 0 deletions manager/cve_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from .base import parse_tags
from .base import PatchRequest
from .base import reporter
from .base import transform_ids
from .base import transform_names
from .base import unique_bool_list
from .filters import apply_filters
from .filters import filter_types
Expand Down Expand Up @@ -82,6 +84,9 @@ def __init__(self, rh_account_id, synopsis, cves_without_errata, list_args, pars
filter_types.SYSTEM_CVE_REMEDIATION,
filter_types.SYSTEM_AAP,
filter_types.SYSTEM_MSSQL,
filter_types.INVENTORY_GROUP_ID,
filter_types.INVENTORY_GROUP_NAME,
filter_types.INVENTORY_GROUP_UNGROUPED,
]

query = apply_filters(query, parsed_args, filters, {})
Expand Down Expand Up @@ -160,6 +165,7 @@ def _full_query(rh_account_id, synopsis):
InventoryHosts.tags,
InventoryHosts.updated,
InventoryHosts.insights_id,
InventoryHosts.groups.alias("inventory_group"),
OS_INFO_QUERY.alias("os"),
fn.COALESCE(SystemVulnerabilities.advisory_available, True).alias("advisory_available"),
fn.COALESCE(SystemVulnerabilities.remediation_type_id, remediation.PLAYBOOK.value).alias("remediation_type_id"),
Expand Down Expand Up @@ -365,6 +371,9 @@ def handle_get(cls, **kwargs): # pylint: disable=too-many-branches
{"arg_name": "remediation", "convert_func": parse_int_list},
{"arg_name": "ansible", "convert_func": None},
{"arg_name": "mssql", "convert_func": None},
{"arg_name": "group_id", "convert_func": transform_ids},
{"arg_name": "group_name", "convert_func": transform_names},
{"arg_name": "ungrouped_hosts", "convert_func": None},
]
args = cls._parse_arguments(kwargs, args_desc)
list_arguments = cls._parse_list_arguments(kwargs)
Expand Down Expand Up @@ -438,6 +447,7 @@ def _build_attributes(sys, advisories_list=None):
record["stale_timestamp"] = sys["stale_timestamp"].isoformat() if sys["stale_timestamp"] else None
record["stale_warning_timestamp"] = sys["stale_warning_timestamp"].isoformat() if sys["stale_warning_timestamp"] else None
record["culled_timestamp"] = sys["culled_timestamp"].isoformat() if sys["culled_timestamp"] else None
record["inventory_group"] = sys.get("inventory_group", [])
return record


Expand Down
60 changes: 60 additions & 0 deletions manager/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from copy import copy
from enum import Enum

from peewee import Expression
from peewee import fn
from peewee import SQL

from common.constants import remediation
from common.peewee_conditions import is_empty_list
Expand Down Expand Up @@ -552,6 +554,61 @@ def _filter_system_by_mssql(query, args, _kwargs):
return query


def _filter_inventory_group_name(query, args, _kwargs):
"""
Args:
query (object): Query object to apply filters to
args (dict): Query arguemnts
Returns:
object: Modified query with xxx filter applied
"""
include_ungrouped = args.get("ungrouped_hosts", False)
if names := args.get("group_name"):
expr = Expression(InventoryHosts.groups, "@>", fn.ANY(SQL(f"'{names}'::jsonb[]")))
if include_ungrouped:
expr |= (InventoryHosts.groups == SQL("\'[]\'"))
query = query.where(expr)
return query


def _filter_inventory_group_id(query, args, _kwargs):
"""
Args:
query (object): Query object to apply filters to
args (dict): Query arguemnts
Returns:
object: Modified query with xxx filter applied
"""
include_ungrouped = args.get("ungrouped_hosts", False)
if ids := args.get("group_id"):
expr = Expression(InventoryHosts.groups, "@>", fn.ANY(SQL(f"'{ids}'::jsonb[]")))
if include_ungrouped:
expr |= (InventoryHosts.groups == SQL("\'[]\'"))
query = query.where(expr)
return query


def _filter_inventory_ungrouped(query, args, _kwargs):
"""
Args:
query (object): Query object to apply filters to
args (dict): Query arguemnts
Returns:
object: Modified query with xxx filter applied
"""
if args.get("group_name") or args.get("group_id"):
return query
if args.get("ungrouped_hosts"):
query = query.where(InventoryHosts.groups == SQL("\'[]\'"))
return query


class filter_types(Enum): # pylint: disable=invalid-name
"""definition of filter types"""

Expand Down Expand Up @@ -580,6 +637,9 @@ class filter_types(Enum): # pylint: disable=invalid-name
SYSTEM_CVE_REMEDIATION = _filter_system_cve_by_remediation
SYSTEM_AAP = _filter_system_by_ansible
SYSTEM_MSSQL = _filter_system_by_mssql
INVENTORY_GROUP_NAME = _filter_inventory_group_name
INVENTORY_GROUP_ID = _filter_inventory_group_id
INVENTORY_GROUP_UNGROUPED = _filter_inventory_ungrouped


def apply_filters(query, args, filters, kwargs):
Expand Down
6 changes: 6 additions & 0 deletions tests/manager_tests/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

_tags = {"namespace": str, "key": str, "value": Or(None, str)}

_inventory_group = {"name": str, "id": str}

_affected_system_attr = {
"inventory_id": str,
"insights_id": Or(None, str),
Expand Down Expand Up @@ -63,6 +65,7 @@
"stale_timestamp": Or(None, str),
"stale_warning_timestamp": Or(None, str),
"mitigation_reason": Or(None, str),
"inventory_group": Or([_inventory_group], []),
}

_affected_system_attr_with_advisories = {"advisories_list": Or([str], [])}
Expand Down Expand Up @@ -343,6 +346,9 @@
"ansible": Or(None, bool),
"mssql": Or(None, bool),
"cves_without_errata": bool,
"group_id": Or(None, str),
"group_name": Or(None, str),
"ungrouped_hosts": Or(None, bool),
}
_affected_systems_meta.update(_meta)

Expand Down
14 changes: 14 additions & 0 deletions tests/manager_tests/test_cve_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ def test_cves_affected_systems_st_3(self):
response = self.vfetch("cves/CVE-2016-1/affected_systems/ids?status_id=4,5,6").check_response()
assert len(response.body.data) == 1

def test_cves_affected_systems_inventory_groups(self):
response = self.vfetch("cves/CVE-2016-1/affected_systems?group_name=group01").check_response()
assert len(response.body.data) == 2
response = self.vfetch("cves/CVE-2016-1/affected_systems?group_id=00000000-1111-0000-0000-000000000000").check_response()
assert len(response.body.data) == 2
response = self.vfetch("cves/CVE-2016-1/affected_systems/ids?group_name=group01").check_response()
assert len(response.body.data) == 2
response = self.vfetch("cves/CVE-2016-1/affected_systems?group_name=group01&ungrouped_hosts=true").check_response()
assert len(response.body.data) == 3
response = self.vfetch("cves/CVE-2016-1/affected_systems?ungrouped_hosts=true").check_response()
assert len(response.body.data) == 1
response = self.vfetch("cves/CVE-2016-1/affected_systems/ids?ungrouped_hosts=true").check_response()
assert len(response.body.data) == 1

def test_invalid_cves_affected_systems(self):
self.vfetch("cves/CVE-INVALID/affected_systems").check_response(status_code=404)
self.vfetch("cves/CVE-INVALID/affected_systems/ids").check_response(status_code=404)
Expand Down

0 comments on commit c1a27f7

Please sign in to comment.