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