Skip to content

Commit f9fa560

Browse files
committed
Add test for Nasl notus functionality
1 parent 5ae3d13 commit f9fa560

File tree

5 files changed

+128
-16
lines changed

5 files changed

+128
-16
lines changed

rust/data/notus/sha256sums

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
98b0943d0ed58ef00b7ae838bbcb22728475bc910527e1f7f0001d52d7651e96 debian_10.notus

rust/src/nasl/builtin/notus/mod.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception
44

5+
#[cfg(test)]
6+
mod tests;
7+
58
use std::{
69
collections::HashMap,
710
io::{Read, Write},
@@ -42,16 +45,18 @@ impl NaslNotus {
4245
) -> Result<NaslValue, FnError> {
4346
let res = notus.scan(product, pkg_list)?;
4447

45-
let mut ret = HashMap::new();
48+
let mut ret = vec![];
4649
for (oid, vuls) in res {
50+
let mut dict = HashMap::new();
4751
let message = vuls.into_iter().map(|vul| match vul.fixed_version {
48-
FixedVersion::Single { version, specifier } => format!("Vulnerable package: {}\nInstalled version: {}\nFixed version: {:2}{}", vul.name, vul.installed_version, specifier.to_string(), version),
49-
FixedVersion::Range { start, end } => format!("Vulnerable package: {}\nInstalled version: {}\nFixed version: < {}\nFixed version: >={}", vul.name, vul.installed_version, start, end),
52+
FixedVersion::Single { version, specifier } => format!("Vulnerable package: {}\nInstalled version: {}-{}\nFixed version: {:2}{}-{}", vul.name, vul.name, vul.installed_version, specifier.to_string(), vul.name, version),
53+
FixedVersion::Range { start, end } => format!("Vulnerable package: {}\nInstalled version: {}-{}\nFixed version: < {}-{}\nFixed version: >={}-{}", vul.name, vul.name, vul.installed_version, vul.name, start, vul.name, end),
5054
}).collect::<Vec<String>>().join("\n\n");
51-
ret.insert(oid, NaslValue::String(message));
55+
dict.insert("oid".to_string(), NaslValue::String(oid));
56+
dict.insert("message".to_string(), NaslValue::String(message));
57+
ret.push(NaslValue::Dict(dict))
5258
}
53-
54-
Ok(NaslValue::Dict(ret))
59+
Ok(NaslValue::Array(ret))
5560
}
5661

5762
fn notus_extern(
@@ -88,12 +93,15 @@ impl NaslNotus {
8893
let results: Vec<NotusResult> = serde_json::from_str(body).unwrap();
8994

9095
// Convert to NaslValue (Dict mapping oid -> message)
91-
let mut ret = HashMap::new();
96+
let mut ret = vec![];
9297
for result in results {
93-
ret.insert(result.oid, NaslValue::String(result.message));
98+
let mut dict = HashMap::new();
99+
dict.insert("oid".to_string(), NaslValue::String(result.oid));
100+
dict.insert("message".to_string(), NaslValue::String(result.message));
101+
ret.push(NaslValue::Dict(dict));
94102
}
95103

96-
Ok(NaslValue::Dict(ret))
104+
Ok(NaslValue::Array(ret))
97105
}
98106

99107
#[nasl_function]
@@ -153,7 +161,7 @@ pub struct NaslNotus {
153161
function_set! {
154162
NaslNotus,
155163
(
156-
NaslNotus::notus_error,
157-
NaslNotus::notus,
164+
(NaslNotus::notus_error, "notus_error"),
165+
(NaslNotus::notus, "notus"),
158166
)
159167
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-FileCopyrightText: 2026 Greenbone AG
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception
4+
5+
use std::{
6+
collections::HashMap,
7+
sync::{Arc, Mutex},
8+
};
9+
10+
use crate::{
11+
nasl::{Loader, test_utils::TestBuilder},
12+
notus::{HashsumProductLoader, Notus},
13+
};
14+
15+
fn make_test_path(sub_components: &[&str]) -> std::path::PathBuf {
16+
let mut path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).to_owned();
17+
for component in sub_components {
18+
path = path.join(component);
19+
}
20+
path.to_owned()
21+
}
22+
23+
pub fn setup_loader() -> HashsumProductLoader {
24+
HashsumProductLoader::new(Loader::from_feed_path(make_test_path(&["data", "notus"])))
25+
}
26+
27+
fn setup() -> Arc<Mutex<Notus<HashsumProductLoader>>> {
28+
let loader = setup_loader();
29+
Arc::new(Mutex::new(Notus::new(loader, false)))
30+
}
31+
32+
#[test]
33+
fn test_notus() {
34+
let notus = setup();
35+
let mut t = TestBuilder::from_notus(notus);
36+
37+
let mut expected = HashMap::new();
38+
expected.insert(
39+
"1.3.6.1.4.1.25623.1.1.7.2.2023.10089729899100".to_string(),
40+
"Vulnerable package: gitlab-ce\nInstalled version: gitlab-ce-16.0.1\nFixed version: < gitlab-ce-16.0.0\nFixed version: >=gitlab-ce-16.0.7"
41+
.to_string(),
42+
);
43+
expected.insert(
44+
"1.3.6.1.4.1.25623.1.1.7.2.2023.0988598199100".to_string(),
45+
"Vulnerable package: grafana\nInstalled version: grafana-8.5.23\nFixed version: >=grafana-8.5.24\n\nVulnerable package: grafana8\nInstalled version: grafana8-8.5.23\nFixed version: >=grafana8-8.5.24"
46+
.to_string(),
47+
);
48+
49+
t.run(r#"notus(product: "debian_10", pkg_list: "gitlab-ce-16.0.1, grafana-8.5.23, grafana8-8.5.23");"#);
50+
51+
t.check_no_errors();
52+
let results = t.results();
53+
assert_eq!(results.len(), 1);
54+
let result = results[0].as_ref().unwrap();
55+
match result {
56+
crate::nasl::NaslValue::Array(items) => {
57+
let mut actual = HashMap::new();
58+
for item in items {
59+
match item {
60+
crate::nasl::NaslValue::Dict(dict) => {
61+
let oid = match dict.get("oid") {
62+
Some(crate::nasl::NaslValue::String(value)) => value.clone(),
63+
_ => panic!("Expected string oid in notus result"),
64+
};
65+
let message = match dict.get("message") {
66+
Some(crate::nasl::NaslValue::String(value)) => value.clone(),
67+
_ => panic!("Expected string message in notus result"),
68+
};
69+
actual.insert(oid, message);
70+
}
71+
_ => panic!("Expected dict items in notus result array"),
72+
}
73+
}
74+
assert_eq!(actual, expected);
75+
}
76+
_ => panic!("Expected array result from notus"),
77+
}
78+
}

rust/src/nasl/test_utils.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ use std::{
88
fmt::{self, Display, Formatter},
99
panic::Location,
1010
path::PathBuf,
11+
sync::{Arc, Mutex},
1112
};
1213

13-
use crate::storage::{ScanID, inmemory::InMemoryStorage};
14-
use crate::{nasl::prelude::*, scanner::preferences::preference::ScanPrefs};
14+
use crate::{nasl::prelude::*, notus::Notus, scanner::preferences::preference::ScanPrefs};
15+
use crate::{
16+
nasl::utils::scan_ctx::NotusCtx,
17+
notus::HashsumProductLoader,
18+
storage::{ScanID, inmemory::InMemoryStorage},
19+
};
1520
use futures::{Stream, StreamExt};
1621

1722
use super::{
@@ -129,6 +134,7 @@ pub struct TestBuilder<S: ContextStorage> {
129134
storage: S,
130135
executor: Executor,
131136
version: NaslVersion,
137+
notus: Option<Arc<Mutex<Notus<HashsumProductLoader>>>>,
132138
}
133139

134140
pub type DefaultTestBuilder = TestBuilder<InMemoryStorage>;
@@ -147,6 +153,7 @@ impl Default for TestBuilder<InMemoryStorage> {
147153
storage: InMemoryStorage::default(),
148154
executor: nasl_std_functions(),
149155
version: NaslVersion::default(),
156+
notus: None,
150157
}
151158
}
152159
}
@@ -172,6 +179,7 @@ where
172179
storage,
173180
executor: nasl_std_functions(),
174181
version: NaslVersion::default(),
182+
notus: None,
175183
}
176184
}
177185
}
@@ -194,11 +202,10 @@ impl TestBuilder<InMemoryStorage> {
194202
storage: InMemoryStorage::default(),
195203
executor: nasl_std_functions(),
196204
version: NaslVersion::default(),
205+
notus: None,
197206
}
198207
}
199-
}
200208

201-
impl TestBuilder<InMemoryStorage> {
202209
/// Construct a `TestBuilder`, immediately run the
203210
/// given code on it and return it.
204211
pub fn from_code(code: impl AsRef<str>) -> Self {
@@ -212,6 +219,23 @@ impl TestBuilder<InMemoryStorage> {
212219
t.run_all(code.as_ref());
213220
t
214221
}
222+
223+
pub fn from_notus(notus: Arc<Mutex<Notus<HashsumProductLoader>>>) -> Self {
224+
Self {
225+
lines: vec![],
226+
results: vec![],
227+
scan_id: Default::default(),
228+
filename: Default::default(),
229+
target: Default::default(),
230+
variables: vec![],
231+
should_verify: true,
232+
loader: Loader::test_empty(),
233+
storage: InMemoryStorage::default(),
234+
executor: nasl_std_functions(),
235+
version: NaslVersion::default(),
236+
notus: Some(notus),
237+
}
238+
}
215239
}
216240

217241
impl<S> TestBuilder<S>
@@ -355,7 +379,7 @@ where
355379
filename: self.filename.clone(),
356380
scan_preferences: ScanPrefs::new(),
357381
alive_test_methods: Vec::default(),
358-
notus: None,
382+
notus: self.notus.as_ref().map(|x| NotusCtx::Direct(x.clone())),
359383
}
360384
.build()
361385
}

rust/src/scannerctl/interpret/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ fn load_feed_by_json(store: &InMemoryStorage, path: &PathBuf) -> Result<(), CliE
124124
Ok(())
125125
}
126126

127+
#[allow(clippy::too_many_arguments)]
127128
async fn run_on_storage<S: ContextStorage>(
128129
storage: S,
129130
loader: Loader,

0 commit comments

Comments
 (0)