Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and enhance version comparison #907

Merged
merged 4 commits into from
Oct 15, 2024
Merged
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
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion entity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ sea-orm = { workspace = true, features = [
] }
serde = { workspace = true }
serde_json = { workspace = true }
strum = { workspace = true, features = ["derive"]}
strum = { workspace = true, features = ["derive"] }
time = { workspace = true }
utoipa = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
log = { workspace = true }
rstest = { workspace = true }
test-context = { workspace = true }
test-log = { workspace = true, features = ["log", "trace"] }
tokio = { workspace = true, features = ["full"] }

trustify-test-context = { workspace = true }
3 changes: 2 additions & 1 deletion entity/src/version_range.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::version_scheme::VersionScheme;
use sea_orm::entity::prelude::*;
use sea_orm::sea_query::{Asterisk, Func, IntoCondition, SimpleExpr};
use trustify_common::db::VersionMatches;
Expand All @@ -8,7 +9,7 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: Uuid,
// The ID of the version scheme, which is a human-friend string key like `semver`.
pub version_scheme_id: String,
pub version_scheme_id: VersionScheme,
pub low_version: Option<String>,
pub low_inclusive: Option<bool>,
pub high_version: Option<String>,
Expand Down
26 changes: 14 additions & 12 deletions entity/src/version_scheme.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "version_scheme")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: String,
pub name: String,
pub description: Option<String>,
#[derive(Copy, Clone, Eq, Hash, Debug, PartialEq, EnumIter, DeriveActiveEnum, strum::Display)]
#[sea_orm(
rs_type = "String",
db_type = "String(StringLen::None)",
rename_all = "camelCase"
)]
#[strum(serialize_all = "camelCase")]
pub enum VersionScheme {
Generic,
Git,
Semver,
Rpm,
Python,
Maven,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}
2 changes: 2 additions & 0 deletions migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ mod m0000631_alter_product_cpe_key;
mod m0000640_create_product_status;
mod m0000650_alter_advisory_tracking;
mod m0000660_purl_id_indexes;
mod m0000670_version_cmp;

pub struct Migrator;

Expand Down Expand Up @@ -173,6 +174,7 @@ impl MigratorTrait for Migrator {
Box::new(m0000640_create_product_status::Migration),
Box::new(m0000650_alter_advisory_tracking::Migration),
Box::new(m0000660_purl_id_indexes::Migration),
Box::new(m0000670_version_cmp::Migration),
]
}
}
Expand Down
44 changes: 44 additions & 0 deletions migration/src/m0000670_version_cmp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
#[allow(deprecated)]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.get_connection()
.execute_unprepared(include_str!("m0000670_version_cmp/version_matches.sql"))
.await
.map(|_| ())?;

manager
.get_connection()
.execute_unprepared(include_str!(
"m0000670_version_cmp/generic_version_matches.sql"
))
.await
.map(|_| ())?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.get_connection()
.execute_unprepared(include_str!(
"m0000620_parallel_unsafe_pg_fns/version_matches.sql"
))
.await
.map(|_| ())?;

manager
.get_connection()
.execute_unprepared(r#"DROP FUNCTION generic_version_matches"#)
.await
.map(|_| ())?;

Ok(())
}
}
28 changes: 28 additions & 0 deletions migration/src/m0000670_version_cmp/generic_version_matches.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- this is just an exact version match
create or replace function generic_version_matches(version_p text, range_p version_range)
returns bool
as
$$
begin
if range_p.low_version is not null then
if range_p.low_inclusive then
if version_p = range_p.low_version then
return true;
end if;
end if;
end if;

if range_p.high_version is not null then
if range_p.high_inclusive then
if version_p = range_p.high_version then
return true;
end if;
end if;
end if;

return false;

end
$$
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could rewrite to be a bit more compact:

create or replace function generic_version_matches(version_p text, range_p version_range)
    returns bool
as
$$
begin
    return (
        (range_p.low_version is not null and range_p.low_inclusive and version_p = range_p.low_version)
        or (range_p.high_version is not null and range_p.high_inclusive and version_p = range_p.high_version)
    );
end
$$ language plpgsql immutable;

untested, I doubt this would result in such a speed up that it is worth reducing readability

language plpgsql immutable;

42 changes: 42 additions & 0 deletions migration/src/m0000670_version_cmp/version_matches.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
create or replace function version_matches(version_p text, range_p version_range)
returns bool
as
$$
declare
begin
-- for an authoritative list of support schemes, see the enum
-- `trustify_entity::version_scheme::VersionScheme`
return case
when range_p.version_scheme_id = 'git'
-- Git is git, and hard.
then gitver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'semver'
-- Semver is semver
then semver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'gem'
-- RubyGems claims to be semver
then semver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'npm'
-- NPM claims to be semver
then semver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'golang'
-- Golang claims to be semver
then semver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'nuget'
-- NuGet claims to be semver
then semver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'generic'
-- Just check if it is equal
then generic_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'rpm'
-- Look at me! I'm an RPM! I'm special!
then rpmver_version_matches(version_p, range_p)
when range_p.version_scheme_id = 'maven'
-- Look at me! I'm a Maven! I'm kinda special!
then maven_version_matches(version_p, range_p)
else
false
end;
end
$$
language plpgsql immutable;
9 changes: 5 additions & 4 deletions modules/fundamental/src/advisory/service/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use trustify_cvss::cvss3::{
AttackComplexity, AttackVector, Availability, Confidentiality, Cvss3Base, Integrity,
PrivilegesRequired, Scope, UserInteraction,
};
use trustify_entity::version_scheme::VersionScheme;
use trustify_module_ingestor::graph::advisory::{
advisory_vulnerability::{VersionInfo, VersionSpec},
AdvisoryContext, AdvisoryInformation,
Expand Down Expand Up @@ -163,7 +164,7 @@ async fn single_advisory(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Purl::from_str("pkg://maven/org.apache/log4j")?,
"fixed",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Maven,
spec: VersionSpec::Exact("1.2.3".to_string()),
},
(),
Expand All @@ -176,7 +177,7 @@ async fn single_advisory(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Purl::from_str("pkg://maven/org.apache/log4j")?,
"fixed",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Maven,
spec: VersionSpec::Exact("1.2.3".to_string()),
},
(),
Expand Down Expand Up @@ -243,7 +244,7 @@ async fn delete_advisory(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Purl::from_str("pkg://maven/org.apache/log4j")?,
"fixed",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Maven,
spec: VersionSpec::Exact("1.2.3".to_string()),
},
(),
Expand All @@ -256,7 +257,7 @@ async fn delete_advisory(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Purl::from_str("pkg://maven/org.apache/log4j")?,
"fixed",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Maven,
spec: VersionSpec::Exact("1.2.3".to_string()),
},
(),
Expand Down
29 changes: 14 additions & 15 deletions modules/ingestor/src/graph/advisory/advisory_vulnerability.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
use crate::graph::advisory::AdvisoryContext;
use crate::graph::error::Error;
use crate::graph::vulnerability::VulnerabilityContext;
use crate::graph::{advisory::AdvisoryContext, error::Error, vulnerability::VulnerabilityContext};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoIdentity, NotSet, QueryFilter, Set};
use sea_query::{Condition, Expr, IntoCondition};
use tracing::instrument;
use trustify_common::cpe::Cpe;
use trustify_common::db::Transactional;
use trustify_common::purl::Purl;
use trustify_common::{cpe::Cpe, db::Transactional, purl::Purl};
use trustify_cvss::cvss3::Cvss3Base;
use trustify_entity as entity;
use trustify_entity::cvss3::Severity;
use trustify_entity::{purl_status, status, version_range, vulnerability};
use trustify_entity::{
self as entity, cvss3::Severity, purl_status, status, version_range,
version_scheme::VersionScheme, vulnerability,
};

#[derive(Clone, Eq, Hash, Debug, PartialEq)]
pub struct VersionInfo {
pub scheme: String,
pub scheme: VersionScheme,
pub spec: VersionSpec,
}

Expand Down Expand Up @@ -438,7 +435,9 @@ impl<'g> AdvisoryVulnerabilityContext<'g> {

#[cfg(test)]
mod test {
use crate::graph::advisory::advisory_vulnerability::{Version, VersionInfo, VersionSpec};
use crate::graph::advisory::advisory_vulnerability::{
Version, VersionInfo, VersionScheme, VersionSpec,
};
use crate::graph::Graph;
use test_context::test_context;
use test_log::test;
Expand Down Expand Up @@ -474,7 +473,7 @@ mod test {
&"pkg://maven/io.quarkus/quarkus-core".try_into()?,
"affected",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Semver,
spec: VersionSpec::Range(
Version::Inclusive("1.0.2".to_string()),
Version::Exclusive("1.2.0".to_string()),
Expand All @@ -490,7 +489,7 @@ mod test {
&"pkg://maven/io.quarkus/quarkus-core".try_into()?,
"not_affected",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Semver,
spec: VersionSpec::Exact("1.1.9".to_string()),
},
Transactional::None,
Expand Down Expand Up @@ -537,7 +536,7 @@ mod test {
&"pkg://maven/io.quarkus/quarkus-core".try_into()?,
"affected",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Semver,
spec: VersionSpec::Range(
Version::Inclusive("1.0.2".to_string()),
Version::Exclusive("1.2.0".to_string()),
Expand All @@ -553,7 +552,7 @@ mod test {
&"pkg://maven/io.quarkus/quarkus-core".try_into()?,
"not_affected",
VersionInfo {
scheme: "semver".to_string(),
scheme: VersionScheme::Semver,
spec: VersionSpec::Exact("1.1.9".to_string()),
},
Transactional::None,
Expand Down
26 changes: 15 additions & 11 deletions modules/ingestor/src/service/advisory/csaf/creator.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
use crate::graph::advisory::advisory_vulnerability::Version;
use crate::graph::product::ProductInformation;
use crate::graph::Graph;
use crate::service::advisory::csaf::product_status::ProductStatus;
use crate::service::advisory::csaf::util::ResolveProductIdCache;
use crate::{
graph::{
advisory::advisory_vulnerability::{VersionInfo, VersionSpec},
advisory::advisory_vulnerability::{Version, VersionInfo, VersionSpec},
cpe::CpeCreator,
product::ProductInformation,
purl::creator::PurlCreator,
Graph,
},
service::{
advisory::csaf::product_status::ProductStatus, advisory::csaf::util::ResolveProductIdCache,
Error,
},
service::Error,
};
use csaf::{definitions::ProductIdT, Csaf};
use sea_orm::{ActiveValue::Set, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter};
use sea_query::IntoCondition;
use std::collections::{hash_map::Entry, HashMap, HashSet};
use tracing::instrument;
use trustify_common::db::Transactional;
use trustify_common::{cpe::Cpe, db::chunk::EntityChunkedIter, purl::Purl};
use trustify_common::{
cpe::Cpe,
db::{chunk::EntityChunkedIter, Transactional},
purl::Purl,
};
use trustify_entity::version_scheme::VersionScheme;
use trustify_entity::{product_status, purl_status, status, version_range};
use uuid::Uuid;

Expand Down Expand Up @@ -167,12 +171,12 @@ impl<'a> StatusCreator<'a> {
// Ingest purl status
let info = match purl.version.clone() {
Some(version) => VersionInfo {
scheme: "generic".to_string(),
scheme: VersionScheme::Generic,
spec: VersionSpec::Exact(version),
},
None => VersionInfo {
spec: VersionSpec::Range(Version::Unbounded, Version::Unbounded),
scheme: "semver".to_string(),
scheme: VersionScheme::Semver,
},
};

Expand Down
Loading