Skip to content

Commit

Permalink
feat: implement product_by_cve api
Browse files Browse the repository at this point in the history
  • Loading branch information
dejanb committed Oct 20, 2023
1 parent 9361e5c commit 6d11cae
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 0 deletions.
15 changes: 15 additions & 0 deletions lib/src/client/intrinsic/certify_vex/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use super::vulnerability::Vulnerability;

#[derive(Debug, Clone)]
pub struct CertifyVEXStatement {
pub vulnerability: Vulnerability,
pub status: VexStatus,
}

#[derive(Debug, Clone)]
pub enum VexStatus {
NotAffected,
Affected,
Fixed,
UnderInvestigation,
}
1 change: 1 addition & 0 deletions lib/src/client/intrinsic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod package;
pub mod path;
pub mod vuln_metadata;
pub mod vulnerability;
pub mod spog;

pub struct IntrinsicGuacClient {
client: GuacClient,
Expand Down
47 changes: 47 additions & 0 deletions lib/src/client/intrinsic/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10312,6 +10312,53 @@
}
}
},
{
"args": [
{
"defaultValue": null,
"description": null,
"name": "vulnerabilityID",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
],
"deprecationReason": null,
"description": "findTopLevelPackagesRelatedToVulnerability takes in a vulnerabilityID string\nand looks for top level packages (i.e. packages with an SBOM) related to the\ncorresponding vulnerability. findTopLevelPackagesRelatedToVulnerability\nreturns a list of Nodes, representing the path from the certify vulnerability\nto a top level package.\n\nAll that is asked in the implementation of this API is that it follows\nthe spirit of helping to retrieve the right nodes with best effort.\n\nWarning: This is an EXPERIMENTAL feature. This is subject to change.\nWarning: This is an OPTIONAL feature. Backends are not required to\nimplement this API.",
"isDeprecated": false,
"name": "findTopLevelPackagesRelatedToVulnerability",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "UNION",
"name": "Node",
"ofType": null
}
}
}
}
}
}
},
{
"args": [
{
Expand Down
181 changes: 181 additions & 0 deletions lib/src/client/intrinsic/spog/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
mod query;

use crate::client::graph::Node;
use crate::client::intrinsic::spog::query::QuerySpog;

use crate::client::intrinsic::{
IntrinsicGuacClient, PackageOrArtifact, PackageOrArtifactInput, PackageOrArtifactSpec,
};
use crate::client::{Error, Id};
use graphql_client::GraphQLQuery;
use graphql_client::reqwest::post_graphql;
use packageurl::PackageUrl;
use serde_json::json;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerability as QS;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnPackage as QSPackage;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnPackageNamespaces as QSPackageNamespaces;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnPackageNamespacesNames as QSPackageNamespacesNames;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnPackageNamespacesNamesVersions as QSPackageNamespacesNamesVersions;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnPackageNamespacesNamesVersionsQualifiers as QSPackageNamespacesNamesVersionsQualifiers;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnCertifyVEXStatement as QSVex;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnCertifyVexStatementVulnerability as QSVexVulnerability;
use crate::client::intrinsic::spog::query::query_spog::QuerySpogFindTopLevelPackagesRelatedToVulnerabilityOnCertifyVexStatementVulnerabilityVulnerabilityIDs as QSVexVulnerabilityID;
use crate::client::intrinsic::spog::query::query_spog::VexStatus;

use super::certify_vex::{CertifyVEXStatement, self};
use super::package::{Package, PackageNamespace, PackageName, PackageVersion, PackageQualifier};
use super::vulnerability::{Vulnerability, VulnerabilityId};


impl IntrinsicGuacClient {

pub async fn product_by_cve(&self, vulnerability_id: &str) -> Result<Vec<ProductByCve>, Error> {
use self::query::query_spog;

let variables = query_spog::Variables {
vulnerability_id: vulnerability_id.to_string(),
};
let response_body =
post_graphql::<QuerySpog, _>(self.client(), self.url(), variables).await?;

if let Some(errors) = response_body.errors {
return Err(Error::GraphQL(errors));
}

let data: <QuerySpog as GraphQLQuery>::ResponseData = response_body.data.ok_or(Error::GraphQL(vec![]))?;
let mut res = Vec::new();

for entry in &data.find_top_level_packages_related_to_vulnerability {
let len = entry.len();
let root = match &entry[len - 1] {
QS::Package(inner) => {
Package::from(inner)
},
_ => return Err(Error::GraphQL(vec![]))
};

let vex = match &entry[0] {
QS::CertifyVEXStatement(inner) => {
CertifyVEXStatement::from(inner)
},
_ => return Err(Error::GraphQL(vec![]))
};
let mut path = Vec::new();
for value in &entry[1..len-1] {
match value {
QS::Package(inner) => {
path.push(Package::from(inner));
},
val => {
//skipping
},
}
}
let item = ProductByCve {
root,
vex,
path,
};
res.push(item);
}

Ok(res)
}
}

#[derive(Debug, Clone)]
pub struct ProductByCve {
pub root: Package,
pub vex: CertifyVEXStatement,
pub path: Vec<Package>,
}

impl From<&QSVexVulnerabilityID> for VulnerabilityId {
fn from(value: &QSVexVulnerabilityID) -> Self {
Self {
vulnerability_id: value.vulnerability_id.clone(),
id: "1".to_string(), //TODO
}
}
}

impl From<&QSVexVulnerability> for Vulnerability {
fn from(value: &QSVexVulnerability) -> Self {
Self {
vulnerability_ids: value.vulnerability_i_ds.iter().map(|e| e.into()).collect(),
r#type: "cve".to_string(),
id: "1".to_string(), //TODO
}
}
}

impl From<&VexStatus> for certify_vex::VexStatus {
fn from(value: &VexStatus) -> Self {
match value {
VexStatus::NOT_AFFECTED => Self::NotAffected,
VexStatus::AFFECTED => Self::Affected,
VexStatus::UNDER_INVESTIGATION => Self::UnderInvestigation,
VexStatus::FIXED => Self::Fixed,
VexStatus::Other(_) => todo!(),
}
}
}

impl From<&QSVex> for CertifyVEXStatement {
fn from(value: &QSVex) -> Self {
Self {
vulnerability: Vulnerability::from(&value.vulnerability),
status: certify_vex::VexStatus::from(&value.status),
}
}
}

impl From<&QSPackage> for Package {
fn from(value: &QSPackage) -> Self {
Self {
id: value.id.clone(),
r#type: value.type_.clone(),
namespaces: value.namespaces.iter().map(|e| e.into()).collect(),
}
}
}

impl From<&QSPackageNamespaces> for PackageNamespace {
fn from(value: &QSPackageNamespaces) -> Self {
Self {
id: value.id.clone(),
namespace: value.namespace.clone(),
names: value.names.iter().map(|e| e.into()).collect(),
}
}
}

impl From<&QSPackageNamespacesNames> for PackageName {
fn from(value: &QSPackageNamespacesNames) -> Self {
Self {
id: value.id.clone(),
name: value.name.clone(),
versions: value.versions.iter().map(|e| e.into()).collect(),
}
}
}

impl From<&QSPackageNamespacesNamesVersions> for PackageVersion {
fn from(value: &QSPackageNamespacesNamesVersions) -> Self {
Self {
id: value.id.clone(),
version: value.version.clone(),
qualifiers: value.qualifiers.iter().map(|e| e.into()).collect(),
subpath: value.subpath.clone(),
}
}
}

impl From<&QSPackageNamespacesNamesVersionsQualifiers> for PackageQualifier {
fn from(value: &QSPackageNamespacesNamesVersionsQualifiers) -> Self {
Self {
key: value.key.clone(),
value: value.value.clone(),
}
}
}
11 changes: 11 additions & 0 deletions lib/src/client/intrinsic/spog/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use graphql_client::GraphQLQuery;

use crate::client::graph::Node;

#[derive(GraphQLQuery)]
#[graphql(
schema_path = "src/client/intrinsic/schema.json",
query_path = "src/client/intrinsic/spog/spog.gql",
response_derives = "Debug, Serialize, Deserialize"
)]
pub struct QuerySpog;
39 changes: 39 additions & 0 deletions lib/src/client/intrinsic/spog/spog.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
query QuerySpog($vulnerabilityID: String!) {
findTopLevelPackagesRelatedToVulnerability(
vulnerabilityID: $vulnerabilityID
) {
__typename
... on CertifyVEXStatement {
vulnerability {
vulnerabilityIDs {
vulnerabilityID
}
}
status
}
... on Package {
id
type
namespaces {
id
namespace
names {
id
name
versions {
id
version
qualifiers {
key
value
}
subpath
}
}
}
}
... on IsDependency {
dependencyType
}
}
}
19 changes: 19 additions & 0 deletions lib/tests/spog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
mod common;

use guac::client::GuacClient;

use crate::common::guac_url;

//TODO do proper testing
// ./bin/guacone collect files --gql-addr http://localhost:8085/query ./rhel-7.9.z.json
// ./bin/guacone collect files --gql-addr http://localhost:8085/query ./cve-2022-2284.json
#[ignore]
#[tokio::test]
async fn product_by_cve() -> Result<(), anyhow::Error> {
let client = GuacClient::new(&"http://localhost:8085/query");

let result = client.intrinsic().product_by_cve("cve-2022-2284").await?;
println!("result {:?}", result);

Ok(())
}

0 comments on commit 6d11cae

Please sign in to comment.