Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/feature_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
OS_EXPOSURE_REPORT_FEATURE = "vulnerability.os_exposure_report"
NEW_NOTIFICATIONS_FEATURE = "vulnerability.new_notifications"
TIMING_LOG_FEATURE = "vulnerability.timing_log"
API_AFFECTED_PACKAGES_FEATURE = "vulnerability.api_affected_packages"


class UnleashClientProxy:
Expand Down
34 changes: 34 additions & 0 deletions common/peewee_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,20 @@ class Meta:
table_name = "package_name"


class Evr(BaseModel):
"""evr table"""

id = AutoField()
epoch = IntegerField(null=False)
version = TextField(null=False)
release = TextField(null=False)

class Meta:
"""evr table metadata"""

table_name = "evr"


class CPE(BaseModel):
"""cpe table"""

Expand Down Expand Up @@ -539,6 +553,8 @@ class SystemVulnerablePackage(BaseModel):
rh_account_id = ForeignKeyField(column_name="rh_account_id", model=RHAccount, field="id")
system_id = ForeignKeyField(column_name="system_id", model=SystemPlatform, field="id")
vulnerable_package_id = ForeignKeyField(column_name="vulnerable_package_id", model=VulnerablePackage, field="id")
affected_evr_id = ForeignKeyField(column_name="affected_evr_id", model=Evr, field="id", null=True)
affected_arch = TextField(null=True)
first_reported = DateTimeField(null=False)

class Meta:
Expand All @@ -547,6 +563,24 @@ class Meta:
table_name = "system_vulnerable_package"


class SystemVulnerabilitiesPackageDetail(BaseModel):
"""system_vulnerabilities_pkg_detail table"""

rh_account_id = ForeignKeyField(column_name="rh_account_id", model=RHAccount, field="id")
system_vulnerability_id = ForeignKeyField(column_name="system_vulnerability_id", model=SystemVulnerabilities, field="id")
package_name_id = ForeignKeyField(column_name="package_name_id", model=PackageName, field="id")
affected_evr_id = ForeignKeyField(column_name="affected_evr_id", model=Evr, field="id")
affected_arch = TextField(null=False)
fixed_evr_id = ForeignKeyField(column_name="fixed_evr_id", model=Evr, field="id", null=True)
fixed_arch = TextField(null=True)

class Meta:
"""system_vulnerabilities_pkg_detail table metadata"""

table_name = "system_vulnerabilities_pkg_detail"
primary_key = False


class SystemCveData(BaseModelRW):
"""system cve data"""

Expand Down
14 changes: 14 additions & 0 deletions develfeatureflags.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@
],
"strategy": "default",
"parameters": {}
},
{
"name": "vulnerability.api_affected_packages",
"type": "release",
"enabled": true,
"stale": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"strategy": "default",
"parameters": {}
}
]
}
55 changes: 55 additions & 0 deletions manager.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,28 @@ paths:
- $ref: '#/components/parameters/report'
- $ref: '#/components/parameters/tags'

/systems/{inventory_id}/cves/{cve_id}:
get:
summary: Get affected and fixed packages for a system CVE
description: >
This endpoint lists package versions affected by a given CVE on a system,
including the fixed version when one is available.
operationId: manager.system_handler.GetSystemCvePackages.get
x-methodName: getAffectedPackagesBySystemCve
security:
- ApiKeyAuth: []
- BasicAuth: []
responses:
200:
description: Affected and fixed package information.
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/SystemCvePackagesOut'
parameters:
- $ref: '#/components/parameters/inventory_id'
- $ref: '#/components/parameters/cve_id'

/systems/{inventory_id}/cves/ids:
get:
summary: Get a CVE identification number report for a system
Expand Down Expand Up @@ -3336,6 +3358,39 @@ components:
- links
- meta

SystemCvePackagesOut:
type: object
properties:
data:
type: array
description: Packages affected by the CVE on the system.
items:
type: object
properties:
package_name:
type: string
description: Package name.
example: kernel
affected_evra:
type: string
description: Installed affected epoch, version, release, and architecture.
example: '0:1.0-1.x86_64'
fixed_evra:
type: string
description: Fixed epoch, version, release, and architecture, when available.
example: '0:1.0-2.x86_64'
nullable: true
required:
- package_name
- affected_evra
- fixed_evra
meta:
type: object
description: Response metadata.
required:
- data
- meta

SystemCvesIdsOut:
type: object
properties:
Expand Down
104 changes: 104 additions & 0 deletions manager/system_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from common.config import Config
from common.constants import HostType
from common.constants import remediation
from common.feature_flags import API_AFFECTED_PACKAGES_FEATURE
from common.feature_flags import UNLEASH
from common.logging import get_logger
from common.peewee_conditions import system_is_active
from common.peewee_conditions import system_is_vulnerable
Expand All @@ -28,15 +30,19 @@
from common.peewee_model import CveAccountData
from common.peewee_model import CveImpact
from common.peewee_model import CveMetadata
from common.peewee_model import Evr
from common.peewee_model import InsightsRule
from common.peewee_model import OperatingSystem
from common.peewee_model import PackageName
from common.peewee_model import RHAccount
from common.peewee_model import SystemCveData
from common.peewee_model import SystemGroupSet
from common.peewee_model import SystemPlatform
from common.peewee_model import SystemTagSet
from common.peewee_model import SystemVulnerabilities
from common.peewee_model import SystemVulnerabilitiesPackageDetail
from common.peewee_model import SystemVulnerablePackage
from common.peewee_model import VulnerablePackage
from common.peewee_model import VulnerablePackageCVE

from .base import CVE_SYNOPSIS_SORT
Expand Down Expand Up @@ -807,6 +813,104 @@ class GetSystemsCvesIds(GetSystemsCves):
_ids_only = True


class GetSystemCvePackages(GetRequest):
"""GET to /v1/systems/{inventory_id}/cves/{cve_id}"""

_endpoint_name = r"/v1/systems/{inventory_id}/cves/{cve_id}"

@staticmethod
def _format_evra(evr, arch):
"""Format separate epoch, version, release, and architecture columns as EVRA."""
return fn.CONCAT(evr.epoch, ":", evr.version, "-", evr.release, ".", arch)

@classmethod
@RBAC.need_permissions(RbacRoutePermissions.VULNERABILITY_RESULTS, allow_system_auth=True)
@RBAC.need_permissions_filter_value(RbacFilterRoutePermissions.REPORTABLE_ENDPOINTS)
@RBACv2.enforce_workspace_permission(RbacRoutePermissions.VULNERABILITY_RESULTS, allow_system_auth=True)
@RBACv2.enforce_workspace_permission_filter_value(RbacFilterRoutePermissions.REPORTABLE_ENDPOINTS)
def handle_get(cls, **kwargs):
"""Return affected and fixed package details for one system-CVE pair."""
if not UNLEASH.is_enabled(API_AFFECTED_PACKAGES_FEATURE):
return cls.format_exception("Access to this endpoint is not allowed.", 403)

rh_account_id = get_account_data(context.context["user"]).id
fixed_evr = Evr.alias("fixed_evr")
fixed_query = (
SystemVulnerabilitiesPackageDetail.select(
PackageName.name.alias("package_name"),
cls._format_evra(Evr, SystemVulnerabilitiesPackageDetail.affected_arch).alias("affected_evra"),
Case(
None,
(
(
(SystemVulnerabilitiesPackageDetail.fixed_evr_id.is_null(False))
& (SystemVulnerabilitiesPackageDetail.fixed_arch.is_null(False)),
cls._format_evra(fixed_evr, SystemVulnerabilitiesPackageDetail.fixed_arch),
),
),
Value(None).cast("text"),
).alias("fixed_evra"),
)
.join(
SystemVulnerabilities,
on=(SystemVulnerabilitiesPackageDetail.system_vulnerability_id == SystemVulnerabilities.id),
)
.join(
SystemPlatform,
on=(SystemVulnerabilities.system_id == SystemPlatform.id) & system_is_active(image=None, stale=None),
)
.join(CveMetadata, on=(SystemVulnerabilities.cve_id == CveMetadata.id))
.switch(SystemVulnerabilitiesPackageDetail)
.join(PackageName, on=(SystemVulnerabilitiesPackageDetail.package_name_id == PackageName.id))
.switch(SystemVulnerabilitiesPackageDetail)
.join(Evr, on=(SystemVulnerabilitiesPackageDetail.affected_evr_id == Evr.id))
.switch(SystemVulnerabilitiesPackageDetail)
.join(
fixed_evr,
JOIN.LEFT_OUTER,
on=(SystemVulnerabilitiesPackageDetail.fixed_evr_id == fixed_evr.id),
)
.where(SystemVulnerabilitiesPackageDetail.rh_account_id == rh_account_id)
.where(SystemVulnerabilities.rh_account_id == rh_account_id)
.where(SystemPlatform.inventory_id == kwargs["inventory_id"])
.where(CveMetadata.cve == kwargs["cve_id"])
)

unfixed_query = (
SystemVulnerablePackage.select(
PackageName.name.alias("package_name"),
cls._format_evra(Evr, SystemVulnerablePackage.affected_arch).alias("affected_evra"),
Value(None).cast("text").alias("fixed_evra"),
)
.join(
SystemPlatform,
on=(SystemVulnerablePackage.system_id == SystemPlatform.id) & system_is_active(image=None, stale=None),
)
.switch(SystemVulnerablePackage)
.join(VulnerablePackage, on=(SystemVulnerablePackage.vulnerable_package_id == VulnerablePackage.id))
.join(VulnerablePackageCVE, on=(VulnerablePackage.id == VulnerablePackageCVE.vulnerable_package_id))
.join(CveMetadata, on=(VulnerablePackageCVE.cve_id == CveMetadata.id))
.switch(VulnerablePackage)
.join(PackageName, on=(VulnerablePackage.package_name_id == PackageName.id))
.switch(SystemVulnerablePackage)
.join(Evr, on=(SystemVulnerablePackage.affected_evr_id == Evr.id))
.where(SystemVulnerablePackage.rh_account_id == rh_account_id)
.where(SystemPlatform.inventory_id == kwargs["inventory_id"])
.where(CveMetadata.cve == kwargs["cve_id"])
.where(SystemVulnerablePackage.affected_evr_id.is_null(False))
.where(SystemVulnerablePackage.affected_arch.is_null(False))
)

if context.context["user"]["identity_type"] == "System":
system_cn = context.context["user"]["system_cn"]
fixed_query = fixed_query.where(SystemPlatform.owner_id == system_cn)
unfixed_query = unfixed_query.where(SystemPlatform.owner_id == system_cn)

fixed_query = SystemGroupSet_join(fixed_query)
unfixed_query = SystemGroupSet_join(unfixed_query)
return {"data": list((fixed_query | unfixed_query).dicts()), "meta": {}}


class GetSystemDetails(GetRequest):
"""GET to /systems/{inventory_id}"""

Expand Down
Loading