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>,
115 pub detail_path: Option<String>,
117 pub reference: Option<String>,
118 #[serde(default)]
119 pub cves: Vec<String>,
120 #[serde(default)]
121 pub endpoints: Vec<String>,
122 #[serde(default)]
123 pub technologies: Vec<String>,
124 pub first_seen: String,
125 pub last_seen: String,
126 pub status: String,
127 pub exploitability: Option<String>,
128 #[serde(default)]
129 pub metadata: Value,
130}
131
132impl Finding {
133 pub fn new(asset_id: &str, tool: &str, title: &str) -> Self {
134 let key = format!("{}:{}:{}", asset_id, tool, title);
135 let mut finding = Self {
136 stable_id: stable_id("finding", &key),
137 asset_id: asset_id.to_string(),
138 tool: tool.to_string(),
139 title: title.to_string(),
140 ..Default::default()
141 };
142 finding.metadata = Value::Object(Default::default());
143 let now = now_utc();
144 finding.first_seen = now.clone();
145 finding.last_seen = now;
146 finding.status = ASSET_ACTIVE.to_string();
147 finding
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
153pub struct Observation {
154 pub stable_id: String,
155 pub run_id: String,
156 pub subject_type: String,
157 pub subject_id: String,
158 pub kind: String,
159 #[serde(default)]
160 pub before: Value,
161 #[serde(default)]
162 pub after: Value,
163 pub detail: String,
164 pub observed_at: String,
165}
166
167impl Observation {
168 pub fn new(
169 run_id: &str,
170 subject_type: &str,
171 subject_id: &str,
172 kind: &str,
173 before: Value,
174 after: Value,
175 detail: &str,
176 ) -> Self {
177 let before_s = before.to_string();
178 let after_s = after.to_string();
179 let key = format!(
180 "{}:{}:{}:{}:{}:{}",
181 run_id, subject_type, subject_id, kind, before_s, after_s
182 );
183 Self {
184 stable_id: stable_id("observation", &key),
185 run_id: run_id.to_string(),
186 subject_type: subject_type.to_string(),
187 subject_id: subject_id.to_string(),
188 kind: kind.to_string(),
189 before,
190 after,
191 detail: detail.to_string(),
192 observed_at: now_utc(),
193 }
194 }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, Default)]
199pub struct ScanRun {
200 pub stable_id: String,
201 pub target: String,
202 pub folder: String,
203 pub started_at: Option<String>,
204 pub finished_at: Option<String>,
205 #[serde(default)]
206 pub tools: Vec<String>,
207 #[serde(default)]
208 pub artifacts: Vec<Value>,
209 pub content_hash: String,
210 pub imported_at: String,
211 #[serde(default)]
212 pub stats: Value,
213}
214
215impl ScanRun {
216 pub fn new(
217 run_id: &str,
218 target: &str,
219 folder: &str,
220 content_hash: &str,
221 started_at: Option<String>,
222 ) -> Self {
223 Self {
224 stable_id: run_id.to_string(),
225 target: target.to_string(),
226 folder: folder.to_string(),
227 started_at,
228 finished_at: None,
229 content_hash: content_hash.to_string(),
230 imported_at: now_utc(),
231 stats: Value::Object(Default::default()),
232 ..Default::default()
233 }
234 }
235}