1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use sha2::{Digest, Sha256};
4use std::collections::BTreeMap;
5use time::format_description::well_known::Rfc3339;
6use time::OffsetDateTime;
7
8pub fn now_utc() -> String {
9 OffsetDateTime::now_utc()
10 .format(&Rfc3339)
11 .unwrap_or_else(|_| format!("{:?}", std::time::SystemTime::now()))
12}
13
14pub fn sha256_hex(input: &str) -> String {
15 let mut hasher = Sha256::new();
16 hasher.update(input.as_bytes());
17 format!("{:x}", hasher.finalize())
18}
19
20fn stable_id(prefix: &str, key: &str) -> String {
21 sha256_hex(&format!("{}:{}", prefix, key))
22}
23
24pub const ASSET_ACTIVE: &str = "active";
25pub const ASSET_REMOVED: &str = "removed";
26
27#[derive(Debug, Clone, Serialize, Deserialize, Default)]
29pub struct Asset {
30 pub stable_id: String,
31 pub asset_type: String,
32 pub name: String,
33 pub ip: Option<String>,
34 pub fqdn: Option<String>,
35 pub reverse_dns: Option<String>,
36 pub first_seen: String,
37 pub last_seen: String,
38 pub status: String,
39 #[serde(default)]
40 pub metadata: Value,
41}
42
43impl Asset {
44 pub fn new(name: &str, asset_type: &str) -> Self {
45 let mut asset = Self {
46 stable_id: stable_id("asset", name),
47 asset_type: asset_type.to_string(),
48 name: name.to_string(),
49 ..Default::default()
50 };
51 asset.metadata = Value::Object(Default::default());
52 let now = now_utc();
53 asset.first_seen = now.clone();
54 asset.last_seen = now;
55 asset.status = ASSET_ACTIVE.to_string();
56 asset
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, Default)]
62pub struct Service {
63 pub stable_id: String,
64 pub asset_id: String,
65 pub port: u16,
66 pub protocol: String,
67 pub service_name: String,
68 pub product: Option<String>,
69 pub version: Option<String>,
70 pub version_str: Option<String>,
71 pub banner: Option<String>,
72 #[serde(default)]
73 pub technologies: Vec<String>,
74 #[serde(default)]
75 pub cpes: Vec<String>,
76 pub first_seen: String,
77 pub last_seen: String,
78 pub status: String,
79 #[serde(default)]
80 pub metadata: Value,
81}
82
83impl Service {
84 pub fn new(asset_id: &str, port: u16, protocol: &str, service_name: &str) -> Self {
85 let key = format!("{}:{}:{}:{}", asset_id, port, protocol, service_name);
86 let mut service = Self {
87 stable_id: stable_id("service", &key),
88 asset_id: asset_id.to_string(),
89 port,
90 protocol: protocol.to_string(),
91 service_name: service_name.to_string(),
92 ..Default::default()
93 };
94 service.metadata = Value::Object(Default::default());
95 let now = now_utc();
96 service.first_seen = now.clone();
97 service.last_seen = now;
98 service.status = ASSET_ACTIVE.to_string();
99 service
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, Default)]
105pub struct Finding {
106 pub stable_id: String,
107 pub asset_id: String,
108 pub service_id: Option<String>,
109 pub title: String,
110 pub severity: String,
111 pub tool: String,
112 pub target_url: Option<String>,
113 pub detail: Option<String>,
116 pub detail_path: Option<String>,
118 pub reference: Option<String>,
119 #[serde(default)]
120 pub cves: Vec<String>,
121 #[serde(default)]
122 pub endpoints: Vec<String>,
123 #[serde(default)]
124 pub technologies: Vec<String>,
125 pub first_seen: String,
126 pub last_seen: String,
127 pub status: String,
128 pub exploitability: Option<String>,
129 #[serde(default)]
130 pub metadata: Value,
131}
132
133impl Finding {
134 pub fn new(asset_id: &str, tool: &str, title: &str) -> Self {
135 let key = format!("{}:{}:{}", asset_id, tool, title);
136 let mut finding = Self {
137 stable_id: stable_id("finding", &key),
138 asset_id: asset_id.to_string(),
139 tool: tool.to_string(),
140 title: title.to_string(),
141 ..Default::default()
142 };
143 finding.metadata = Value::Object(Default::default());
144 let now = now_utc();
145 finding.first_seen = now.clone();
146 finding.last_seen = now;
147 finding.status = ASSET_ACTIVE.to_string();
148 finding
149 }
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, Default)]
154pub struct Observation {
155 pub stable_id: String,
156 pub run_id: String,
157 pub subject_type: String,
158 pub subject_id: String,
159 pub kind: String,
160 #[serde(default)]
161 pub before: Value,
162 #[serde(default)]
163 pub after: Value,
164 pub detail: String,
165 pub observed_at: String,
166}
167
168impl Observation {
169 pub fn new(
170 run_id: &str,
171 subject_type: &str,
172 subject_id: &str,
173 kind: &str,
174 before: Value,
175 after: Value,
176 detail: &str,
177 ) -> Self {
178 let before_s = before.to_string();
179 let after_s = after.to_string();
180 let key = format!(
181 "{}:{}:{}:{}:{}:{}",
182 run_id, subject_type, subject_id, kind, before_s, after_s
183 );
184 Self {
185 stable_id: stable_id("observation", &key),
186 run_id: run_id.to_string(),
187 subject_type: subject_type.to_string(),
188 subject_id: subject_id.to_string(),
189 kind: kind.to_string(),
190 before,
191 after,
192 detail: detail.to_string(),
193 observed_at: now_utc(),
194 }
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, Default)]
200pub struct ScanRun {
201 pub stable_id: String,
202 pub target: String,
203 pub folder: String,
204 pub started_at: Option<String>,
205 pub finished_at: Option<String>,
206 #[serde(default)]
207 pub tools: Vec<String>,
208 #[serde(default)]
209 pub artifacts: Vec<Value>,
210 pub content_hash: String,
211 pub imported_at: String,
212 #[serde(default)]
213 pub stats: Value,
214}
215
216impl ScanRun {
217 pub fn new(
218 run_id: &str,
219 target: &str,
220 folder: &str,
221 content_hash: &str,
222 started_at: Option<String>,
223 ) -> Self {
224 Self {
225 stable_id: run_id.to_string(),
226 target: target.to_string(),
227 folder: folder.to_string(),
228 started_at,
229 finished_at: None,
230 content_hash: content_hash.to_string(),
231 imported_at: now_utc(),
232 stats: Value::Object(Default::default()),
233 ..Default::default()
234 }
235 }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, Default)]
242pub struct ExploitExecution {
243 pub execution_id: String,
244 pub run_id: String,
245 pub asset_id: String,
246 pub vulnerability_id: String,
247 pub module_type: String,
248 pub module: String,
249 pub host: String,
250 pub payload: Option<String>,
251 pub status: String,
252 pub start_time: String,
253 pub finish_time: Option<String>,
254 pub job_id: Option<String>,
255 pub session_id: Option<String>,
256 pub error: Option<String>,
257 #[serde(default)]
258 pub loot: Vec<String>,
259 #[serde(default)]
260 pub options: BTreeMap<String, String>,
261 pub selected: bool,
263 pub created_at: String,
264}
265
266impl ExploitExecution {
267 pub fn new(run_id: &str, asset_id: &str, vulnerability_id: &str, module: &str) -> Self {
268 let key = format!("{}:{}:{}:{}", run_id, asset_id, vulnerability_id, module);
269 let now = now_utc();
270 Self {
271 execution_id: sha256_hex(&format!("exploit_exec:{}:{}", key, now)),
272 run_id: run_id.to_string(),
273 asset_id: asset_id.to_string(),
274 vulnerability_id: vulnerability_id.to_string(),
275 module: module.to_string(),
276 module_type: String::new(),
277 host: String::new(),
278 payload: None,
279 status: String::new(),
280 start_time: now.clone(),
281 finish_time: None,
282 job_id: None,
283 session_id: None,
284 error: None,
285 loot: Vec::new(),
286 options: BTreeMap::new(),
287 selected: false,
288 created_at: now,
289 }
290 }
291}