Skip to content

Commit 0e3f275

Browse files
committed
feat: add domain model for scan result
1 parent 7892e3e commit 0e3f275

24 files changed

+1892
-5
lines changed

flake.nix

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,18 @@
4040
mkShell {
4141
packages = [
4242
cargo
43-
rustc
44-
rustfmt
4543
cargo-audit
46-
cargo-watch
47-
cargo-nextest
4844
cargo-expand
45+
cargo-nextest
46+
cargo-tarpaulin
47+
cargo-watch
4948
clippy
5049
just
51-
rust-analyzer
5250
lldb
5351
pre-commit
52+
rust-analyzer
53+
rustc
54+
rustfmt
5455
];
5556

5657
inputsFrom = [ sysdig-lsp ];

src/domain/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod scanresult;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use crate::domain::scanresult::accepted_risk_reason::AcceptedRiskReason;
2+
use crate::domain::scanresult::package::Package;
3+
use crate::domain::scanresult::vulnerability::Vulnerability;
4+
use crate::domain::scanresult::weak_hash::WeakHash;
5+
use chrono::{DateTime, Utc};
6+
use std::collections::HashSet;
7+
use std::fmt::Debug;
8+
use std::hash::{Hash, Hasher};
9+
use std::sync::{Arc, RwLock};
10+
11+
pub struct AcceptedRisk {
12+
id: String,
13+
reason: AcceptedRiskReason,
14+
description: String,
15+
expiration_date: Option<DateTime<Utc>>,
16+
is_active: bool,
17+
created_at: DateTime<Utc>,
18+
updated_at: DateTime<Utc>,
19+
assigned_to_vulnerabilities: RwLock<HashSet<WeakHash<Vulnerability>>>,
20+
assigned_to_packages: RwLock<HashSet<WeakHash<Package>>>,
21+
}
22+
23+
impl Debug for AcceptedRisk {
24+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25+
f.debug_struct("AcceptedRisk")
26+
.field("id", &self.id)
27+
.field("reason", &self.reason)
28+
.field("description", &self.description)
29+
.field("expiration_date", &self.expiration_date)
30+
.field("is_active", &self.is_active)
31+
.field("created_at", &self.created_at)
32+
.field("updated_at", &self.updated_at)
33+
.finish()
34+
}
35+
}
36+
37+
impl AcceptedRisk {
38+
#[allow(clippy::too_many_arguments)]
39+
pub(in crate::domain::scanresult) fn new(
40+
id: String,
41+
reason: AcceptedRiskReason,
42+
description: String,
43+
expiration_date: Option<DateTime<Utc>>,
44+
is_active: bool,
45+
created_at: DateTime<Utc>,
46+
updated_at: DateTime<Utc>,
47+
) -> Self {
48+
Self {
49+
id,
50+
reason,
51+
description,
52+
expiration_date,
53+
is_active,
54+
created_at,
55+
updated_at,
56+
assigned_to_vulnerabilities: RwLock::new(HashSet::new()),
57+
assigned_to_packages: RwLock::new(HashSet::new()),
58+
}
59+
}
60+
61+
pub fn id(&self) -> &str {
62+
&self.id
63+
}
64+
65+
pub fn reason(&self) -> &AcceptedRiskReason {
66+
&self.reason
67+
}
68+
69+
pub fn description(&self) -> &str {
70+
&self.description
71+
}
72+
73+
pub fn expiration_date(&self) -> Option<DateTime<Utc>> {
74+
self.expiration_date
75+
}
76+
77+
pub fn is_active(&self) -> bool {
78+
self.is_active
79+
}
80+
81+
pub fn created_at(&self) -> DateTime<Utc> {
82+
self.created_at
83+
}
84+
85+
pub fn updated_at(&self) -> DateTime<Utc> {
86+
self.updated_at
87+
}
88+
89+
pub fn add_for_vulnerability(self: &Arc<Self>, vulnerability: Arc<Vulnerability>) {
90+
if self
91+
.assigned_to_vulnerabilities
92+
.write()
93+
.unwrap()
94+
.insert(WeakHash(Arc::downgrade(&vulnerability)))
95+
{
96+
vulnerability.add_accepted_risk(self.clone());
97+
}
98+
}
99+
100+
pub fn assigned_to_vulnerabilities(self: &Arc<Self>) -> Vec<Arc<Vulnerability>> {
101+
self.assigned_to_vulnerabilities
102+
.read()
103+
.unwrap()
104+
.iter()
105+
.filter_map(|v| v.0.upgrade())
106+
.collect()
107+
}
108+
109+
pub fn add_for_package(self: &Arc<Self>, a_package: Arc<Package>) {
110+
if self
111+
.assigned_to_packages
112+
.write()
113+
.unwrap()
114+
.insert(WeakHash(Arc::downgrade(&a_package)))
115+
{
116+
a_package.add_accepted_risk(self.clone());
117+
}
118+
}
119+
120+
pub fn assigned_to_packages(self: &Arc<Self>) -> Vec<Arc<Package>> {
121+
self.assigned_to_packages
122+
.read()
123+
.unwrap()
124+
.iter()
125+
.filter_map(|p| p.0.upgrade())
126+
.collect()
127+
}
128+
}
129+
130+
impl PartialEq for AcceptedRisk {
131+
fn eq(&self, other: &Self) -> bool {
132+
self.id == other.id
133+
}
134+
}
135+
136+
impl Eq for AcceptedRisk {}
137+
138+
impl Hash for AcceptedRisk {
139+
fn hash<H: Hasher>(&self, state: &mut H) {
140+
self.id.hash(state);
141+
}
142+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2+
pub enum AcceptedRiskReason {
3+
RiskOwned,
4+
RiskTransferred,
5+
RiskAvoided,
6+
RiskMitigated,
7+
RiskNotRelevant,
8+
Custom,
9+
Unknown,
10+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
2+
pub enum Architecture {
3+
Amd64,
4+
Arm64,
5+
Unknown,
6+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
2+
pub enum EvaluationResult {
3+
Passed,
4+
Failed,
5+
}
6+
7+
impl EvaluationResult {
8+
pub fn is_failed(&self) -> bool {
9+
matches!(self, Self::Failed)
10+
}
11+
12+
pub fn is_passed(&self) -> bool {
13+
matches!(self, Self::Passed)
14+
}
15+
}

src/domain/scanresult/layer.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::domain::scanresult::package::Package;
2+
use crate::domain::scanresult::vulnerability::Vulnerability;
3+
use std::collections::HashSet;
4+
use std::fmt::Debug;
5+
use std::hash::{Hash, Hasher};
6+
use std::sync::{Arc, RwLock};
7+
8+
pub struct Layer {
9+
digest: String,
10+
size: Option<u64>,
11+
command: String,
12+
packages: RwLock<HashSet<Arc<Package>>>,
13+
}
14+
15+
impl Debug for Layer {
16+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
f.debug_struct("Layer")
18+
.field("digest", &self.digest)
19+
.field("size", &self.size)
20+
.field("command", &self.command)
21+
.finish()
22+
}
23+
}
24+
25+
impl Layer {
26+
pub(in crate::domain::scanresult) fn new(
27+
digest: String,
28+
size: Option<u64>,
29+
command: String,
30+
) -> Self {
31+
Self {
32+
digest,
33+
size,
34+
command,
35+
packages: RwLock::new(HashSet::new()),
36+
}
37+
}
38+
39+
pub fn digest(&self) -> Option<&str> {
40+
if self.digest.is_empty() {
41+
None
42+
} else {
43+
Some(&self.digest)
44+
}
45+
}
46+
47+
pub fn size(&self) -> Option<&u64> {
48+
self.size.as_ref()
49+
}
50+
51+
pub fn command(&self) -> &str {
52+
&self.command
53+
}
54+
55+
pub(in crate::domain::scanresult) fn add_package(&self, a_package: Arc<Package>) {
56+
self.packages.write().unwrap().insert(a_package);
57+
}
58+
59+
pub fn packages(&self) -> Vec<Arc<Package>> {
60+
self.packages.read().unwrap().iter().cloned().collect()
61+
}
62+
63+
pub fn vulnerabilities(&self) -> Vec<Arc<Vulnerability>> {
64+
self.packages
65+
.read()
66+
.unwrap()
67+
.iter()
68+
.flat_map(|p| p.vulnerabilities())
69+
.collect()
70+
}
71+
}
72+
73+
impl PartialEq for Layer {
74+
fn eq(&self, other: &Self) -> bool {
75+
self.digest == other.digest
76+
}
77+
}
78+
79+
impl Eq for Layer {}
80+
81+
impl Hash for Layer {
82+
fn hash<H: Hasher>(&self, state: &mut H) {
83+
self.digest.hash(state);
84+
}
85+
}

src/domain/scanresult/metadata.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crate::domain::scanresult::architecture::Architecture;
2+
use crate::domain::scanresult::operating_system::OperatingSystem;
3+
use chrono::{DateTime, Utc};
4+
use std::collections::HashMap;
5+
6+
#[derive(PartialEq, Eq)]
7+
pub struct Metadata {
8+
pull_string: String,
9+
image_id: String,
10+
digest: String,
11+
base_os: OperatingSystem,
12+
size_in_bytes: u64,
13+
architecture: Architecture,
14+
labels: HashMap<String, String>,
15+
created_at: DateTime<Utc>,
16+
}
17+
18+
impl Metadata {
19+
#[allow(clippy::too_many_arguments)]
20+
pub(in crate::domain::scanresult) fn new(
21+
pull_string: String,
22+
image_id: String,
23+
digest: String,
24+
base_os: OperatingSystem,
25+
size_in_bytes: u64,
26+
architecture: Architecture,
27+
labels: HashMap<String, String>,
28+
created_at: DateTime<Utc>,
29+
) -> Self {
30+
Self {
31+
pull_string,
32+
image_id,
33+
digest,
34+
base_os,
35+
size_in_bytes,
36+
architecture,
37+
labels,
38+
created_at,
39+
}
40+
}
41+
42+
pub fn pull_string(&self) -> &str {
43+
&self.pull_string
44+
}
45+
46+
pub fn image_id(&self) -> &str {
47+
&self.image_id
48+
}
49+
50+
pub fn digest(&self) -> Option<&str> {
51+
if self.digest.is_empty() {
52+
None
53+
} else {
54+
Some(&self.digest)
55+
}
56+
}
57+
58+
pub fn base_os(&self) -> &OperatingSystem {
59+
&self.base_os
60+
}
61+
62+
pub fn size_in_bytes(&self) -> &u64 {
63+
&self.size_in_bytes
64+
}
65+
66+
pub fn architecture(&self) -> &Architecture {
67+
&self.architecture
68+
}
69+
70+
pub fn labels(&self) -> &HashMap<String, String> {
71+
&self.labels
72+
}
73+
74+
pub fn created_at(&self) -> DateTime<Utc> {
75+
self.created_at
76+
}
77+
}

src/domain/scanresult/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pub mod accepted_risk;
2+
pub mod accepted_risk_reason;
3+
pub mod architecture;
4+
pub mod evaluation_result;
5+
pub mod layer;
6+
pub mod metadata;
7+
pub mod operating_system;
8+
pub mod package;
9+
pub mod package_type;
10+
pub mod policy;
11+
pub mod policy_bundle;
12+
pub mod policy_bundle_rule;
13+
pub mod policy_bundle_rule_failure;
14+
pub mod policy_bundle_rule_image_config_failure;
15+
pub mod policy_bundle_rule_pkg_vuln_failure;
16+
pub mod scan_result;
17+
pub mod scan_type;
18+
pub mod severity;
19+
pub mod vulnerability;
20+
pub mod weak_hash;

0 commit comments

Comments
 (0)