Skip to content

Commit 012d4d4

Browse files
committed
feat: ingest scores
1 parent d08d508 commit 012d4d4

File tree

13 files changed

+404
-50
lines changed

13 files changed

+404
-50
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

entity/src/advisory_vulnerability.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ pub enum Relation {
3737

3838
#[sea_orm(has_many = "super::purl_status::Entity")]
3939
PurlStatus,
40+
41+
#[sea_orm(has_many = "super::advisory_vulnerability_score::Entity")]
42+
Score,
4043
}
4144

4245
impl Related<advisory::Entity> for Entity {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use crate::{advisory, advisory_vulnerability, cvss3, vulnerability};
2+
use sea_orm::entity::prelude::*;
3+
4+
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
5+
#[sea_orm(table_name = "advisory_vulnerability_score")]
6+
pub struct Model {
7+
#[sea_orm(primary_key)]
8+
pub id: Uuid,
9+
10+
pub advisory_id: Uuid,
11+
pub vulnerability_id: String,
12+
13+
pub r#type: ScoreType,
14+
pub vector: String,
15+
pub score: f64,
16+
pub severity: Severity,
17+
}
18+
19+
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
20+
pub enum Relation {
21+
#[sea_orm(
22+
belongs_to = "super::advisory_vulnerability::Entity",
23+
from = "(super::advisory_vulnerability_score::Column::AdvisoryId, super::advisory_vulnerability_score::Column::VulnerabilityId)"
24+
to = "(super::advisory_vulnerability::Column::AdvisoryId, super::advisory_vulnerability::Column::VulnerabilityId)"
25+
)]
26+
AdvisoryVulnerability,
27+
#[sea_orm(
28+
belongs_to = "super::advisory::Entity",
29+
from = "super::advisory_vulnerability_score::Column::AdvisoryId"
30+
to = "super::advisory::Column::Id"
31+
)]
32+
Advisory,
33+
#[sea_orm(
34+
belongs_to = "super::vulnerability::Entity",
35+
from = "super::advisory_vulnerability_score::Column::VulnerabilityId"
36+
to = "super::vulnerability::Column::Id"
37+
)]
38+
Vulnerability,
39+
}
40+
41+
impl Related<advisory::Entity> for Entity {
42+
fn to() -> RelationDef {
43+
Relation::Advisory.def()
44+
}
45+
}
46+
47+
impl Related<vulnerability::Entity> for Entity {
48+
fn to() -> RelationDef {
49+
Relation::Vulnerability.def()
50+
}
51+
}
52+
53+
impl Related<advisory_vulnerability::Entity> for Entity {
54+
fn to() -> RelationDef {
55+
Relation::AdvisoryVulnerability.def()
56+
}
57+
}
58+
59+
impl ActiveModelBehavior for ActiveModel {}
60+
61+
// score type
62+
63+
#[derive(Debug, Copy, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
64+
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "score_type")]
65+
pub enum ScoreType {
66+
#[sea_orm(string_value = "2.0")]
67+
V2_0,
68+
#[sea_orm(string_value = "3.0")]
69+
V3_0,
70+
#[sea_orm(string_value = "3.1")]
71+
V3_1,
72+
#[sea_orm(string_value = "4.0")]
73+
V4_0,
74+
}
75+
76+
// severity
77+
78+
#[derive(
79+
Debug,
80+
Copy,
81+
Clone,
82+
PartialEq,
83+
Eq,
84+
EnumIter,
85+
DeriveActiveEnum,
86+
strum::EnumString,
87+
strum::Display,
88+
strum::VariantNames,
89+
)]
90+
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "severity")]
91+
#[strum(serialize_all = "lowercase")]
92+
pub enum Severity {
93+
#[sea_orm(string_value = "none")]
94+
None,
95+
#[sea_orm(string_value = "low")]
96+
Low,
97+
#[sea_orm(string_value = "medium")]
98+
Medium,
99+
#[sea_orm(string_value = "high")]
100+
High,
101+
#[sea_orm(string_value = "critical")]
102+
Critical,
103+
}
104+
105+
impl From<cvss3::Severity> for Severity {
106+
fn from(value: cvss3::Severity) -> Self {
107+
match value {
108+
cvss3::Severity::None => Self::None,
109+
cvss3::Severity::Low => Self::Low,
110+
cvss3::Severity::Medium => Self::Medium,
111+
cvss3::Severity::High => Self::High,
112+
cvss3::Severity::Critical => Self::Critical,
113+
}
114+
}
115+
}
116+
117+
impl From<Severity> for cvss3::Severity {
118+
fn from(value: Severity) -> Self {
119+
match value {
120+
Severity::None => Self::None,
121+
Severity::Low => Self::Low,
122+
Severity::Medium => Self::Medium,
123+
Severity::High => Self::High,
124+
Severity::Critical => Self::Critical,
125+
}
126+
}
127+
}

entity/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod advisory;
22
pub mod advisory_vulnerability;
3+
pub mod advisory_vulnerability_score;
34
pub mod base_purl;
45
pub mod conversation;
56
pub mod cpe;

migration/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ path = "src/lib.rs"
1212
[dependencies]
1313
trustify-common = { workspace = true }
1414
trustify-entity = { workspace = true }
15+
trustify-module-ingestor = { workspace = true }
1516
trustify-module-storage = { workspace = true }
1617

1718
anyhow = { workspace = true }
@@ -29,6 +30,7 @@ sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls", "sql
2930
serde-cyclonedx = { workspace = true }
3031
serde_json = { workspace = true }
3132
spdx-rs = { workspace = true }
33+
strum = { workspace = true, features = ["derive"] }
3234
tokio = { workspace = true, features = ["full"] }
3335
tracing = { workspace = true }
3436
tracing-subscriber = { workspace = true }

migration/src/data/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ macro_rules! sbom {
194194
///
195195
/// See: [`handler!`].
196196
#[macro_export]
197-
macro_rules! advisories {
197+
macro_rules! advisory {
198198
(async | $doc:ident, $model:ident, $tx:ident | $body:block) => {
199199
$crate::handler!(async |$doc: $crate::data::Advisory, $model, $tx| $body)
200200
};

migration/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl Migrator {
7272
.normal(m0001160_improve_expand_spdx_licenses_function::Migration)
7373
.normal(m0001170_non_null_source_document_id::Migration)
7474
.data(m0002000_example_sbom_data_migration::Migration)
75-
.data(m0002010_)
75+
.data(m0002010_example_advisory_data_migration::Migration)
7676
}
7777
}
7878

Lines changed: 121 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use crate::{
2-
data::{MigrationTraitWithData, Sbom as SbomDoc, SchemaDataManager},
3-
sbom,
2+
advisory,
3+
data::{Advisory as AdvisoryDoc, MigrationTraitWithData, SchemaDataManager},
44
};
5-
use sea_orm::{ActiveModelTrait, IntoActiveModel, Set};
5+
use sea_orm::{EntityTrait, IntoActiveModel, sea_query::extension::postgres::Type};
66
use sea_orm_migration::prelude::*;
7-
use trustify_common::advisory::cyclonedx::extract_properties_json;
7+
use strum::VariantNames;
8+
use trustify_entity::{advisory, advisory_vulnerability_score};
9+
use trustify_module_ingestor::{graph::cvss::ScoreCreator, service::advisory::osv::extract_scores};
810

911
#[derive(DeriveMigrationName)]
1012
pub struct Migration;
@@ -13,47 +15,92 @@ pub struct Migration;
1315
impl MigrationTraitWithData for Migration {
1416
async fn up(&self, manager: &SchemaDataManager) -> Result<(), DbErr> {
1517
manager
16-
.alter_table(
17-
Table::alter()
18-
.table(Sbom::Table)
19-
.add_column_if_not_exists(
20-
ColumnDef::new(Sbom::Properties)
21-
.json()
22-
.default(serde_json::Value::Null)
23-
.to_owned(),
24-
)
18+
.create_type(
19+
Type::create()
20+
.as_enum(Severity::Table)
21+
.values(Severity::VARIANTS.iter().skip(1).copied())
2522
.to_owned(),
2623
)
2724
.await?;
2825

2926
manager
30-
.alter_table(
31-
Table::alter()
32-
.table(Sbom::Table)
33-
.modify_column(ColumnDef::new(Sbom::Properties).not_null().to_owned())
27+
.create_type(
28+
Type::create()
29+
.as_enum(ScoreType::Table)
30+
.values(ScoreType::VARIANTS.iter().skip(1).copied())
31+
.to_owned(),
32+
)
33+
.await?;
34+
35+
manager
36+
.create_table(
37+
Table::create()
38+
.table(AdvisoryVulnerabilityScore::Table)
39+
.if_not_exists()
40+
.col(
41+
ColumnDef::new(AdvisoryVulnerabilityScore::Id)
42+
.uuid()
43+
.not_null()
44+
.primary_key()
45+
.to_owned(),
46+
)
47+
.col(
48+
ColumnDef::new(AdvisoryVulnerabilityScore::AdvisoryId)
49+
.uuid()
50+
.not_null()
51+
.to_owned(),
52+
)
53+
.col(
54+
ColumnDef::new(AdvisoryVulnerabilityScore::VulnerabilityId)
55+
.uuid()
56+
.not_null()
57+
.to_owned(),
58+
)
59+
.col(
60+
ColumnDef::new(AdvisoryVulnerabilityScore::Type)
61+
.custom(ScoreType::Table)
62+
.not_null()
63+
.to_owned(),
64+
)
65+
.col(
66+
ColumnDef::new(AdvisoryVulnerabilityScore::Vector)
67+
.string()
68+
.not_null()
69+
.to_owned(),
70+
)
71+
.col(
72+
ColumnDef::new(AdvisoryVulnerabilityScore::Score)
73+
.float()
74+
.not_null()
75+
.to_owned(),
76+
)
77+
.col(
78+
ColumnDef::new(AdvisoryVulnerabilityScore::Severity)
79+
.custom(Severity::Table)
80+
.not_null()
81+
.to_owned(),
82+
)
3483
.to_owned(),
3584
)
3685
.await?;
3786

3887
manager
3988
.process(
4089
self,
41-
sbom!(async |sbom, model, tx| {
42-
let mut model = model.into_active_model();
43-
match sbom {
44-
SbomDoc::CycloneDx(sbom) => {
45-
model.properties = Set(extract_properties_json(&sbom));
90+
advisory!(async |advisory, model, tx| {
91+
match advisory {
92+
AdvisoryDoc::Cve(advisory) => {}
93+
AdvisoryDoc::Csaf(advisory) => {}
94+
AdvisoryDoc::Osv(advisory) => {
95+
let mut creator = ScoreCreator::new(model.id);
96+
extract_scores(&advisory, &mut creator);
97+
creator.create(tx).await?;
4698
}
47-
SbomDoc::Spdx(_sbom) => {
48-
model.properties = Set(serde_json::Value::Object(Default::default()));
49-
}
50-
SbomDoc::Other(_) => {
99+
_ => {
51100
// we ignore others
52101
}
53102
}
54103

55-
model.save(tx).await?;
56-
57104
Ok(())
58105
}),
59106
)
@@ -64,20 +111,60 @@ impl MigrationTraitWithData for Migration {
64111

65112
async fn down(&self, manager: &SchemaDataManager) -> Result<(), DbErr> {
66113
manager
67-
.alter_table(
68-
Table::alter()
69-
.table(Sbom::Table)
70-
.drop_column(Sbom::Properties)
114+
.drop_table(
115+
Table::drop()
116+
.table(AdvisoryVulnerabilityScore::Table)
117+
.if_exists()
71118
.to_owned(),
72119
)
73120
.await?;
74121

122+
manager
123+
.drop_type(Type::drop().if_exists().name("severity").to_owned())
124+
.await?;
125+
126+
manager
127+
.drop_type(Type::drop().if_exists().name("score_type").to_owned())
128+
.await?;
129+
75130
Ok(())
76131
}
77132
}
78133

79134
#[derive(DeriveIden)]
80-
enum Sbom {
135+
enum AdvisoryVulnerabilityScore {
136+
Table,
137+
Id,
138+
AdvisoryId,
139+
VulnerabilityId,
140+
Type,
141+
Vector,
142+
Score,
143+
Severity,
144+
}
145+
146+
#[derive(DeriveIden, strum::VariantNames, strum::Display)]
147+
#[allow(unused)]
148+
enum ScoreType {
149+
Table,
150+
#[strum(to_string = "2.0")]
151+
V2_0,
152+
#[strum(to_string = "3.0")]
153+
V3_0,
154+
#[strum(to_string = "3.1")]
155+
V3_1,
156+
#[strum(to_string = "4.0")]
157+
V4_0,
158+
}
159+
160+
#[derive(DeriveIden, strum::VariantNames, strum::Display)]
161+
#[strum(serialize_all = "lowercase")]
162+
#[allow(unused)]
163+
enum Severity {
81164
Table,
82-
Properties,
165+
None,
166+
Low,
167+
Medium,
168+
High,
169+
Critical,
83170
}

0 commit comments

Comments
 (0)