use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::{Digest, Sha256};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
pub fn now_utc() -> String {
OffsetDateTime::now_utc()
.format(&Rfc3339)
.unwrap_or_else(|_| format!("{:?}", std::time::SystemTime::now()))
}
pub fn sha256_hex(input: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
format!("{:x}", hasher.finalize())
}
fn stable_id(prefix: &str, key: &str) -> String {
sha256_hex(&format!("{}:{}", prefix, key))
}
pub const ASSET_ACTIVE: &str = "active";
pub const ASSET_REMOVED: &str = "removed";
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Asset {
pub stable_id: String,
pub asset_type: String,
pub name: String,
pub ip: Option<String>,
pub fqdn: Option<String>,
pub reverse_dns: Option<String>,
pub first_seen: String,
pub last_seen: String,
pub status: String,
#[serde(default)]
pub metadata: Value,
}
impl Asset {
pub fn new(name: &str, asset_type: &str) -> Self {
let mut asset = Self {
stable_id: stable_id("asset", name),
asset_type: asset_type.to_string(),
name: name.to_string(),
..Default::default()
};
asset.metadata = Value::Object(Default::default());
let now = now_utc();
asset.first_seen = now.clone();
asset.last_seen = now;
asset.status = ASSET_ACTIVE.to_string();
asset
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Service {
pub stable_id: String,
pub asset_id: String,
pub port: u16,
pub protocol: String,
pub service_name: String,
pub product: Option<String>,
pub version: Option<String>,
pub version_str: Option<String>,
pub banner: Option<String>,
#[serde(default)]
pub technologies: Vec<String>,
#[serde(default)]
pub cpes: Vec<String>,
pub first_seen: String,
pub last_seen: String,
pub status: String,
#[serde(default)]
pub metadata: Value,
}
impl Service {
pub fn new(asset_id: &str, port: u16, protocol: &str, service_name: &str) -> Self {
let key = format!("{}:{}:{}:{}", asset_id, port, protocol, service_name);
let mut service = Self {
stable_id: stable_id("service", &key),
asset_id: asset_id.to_string(),
port,
protocol: protocol.to_string(),
service_name: service_name.to_string(),
..Default::default()
};
service.metadata = Value::Object(Default::default());
let now = now_utc();
service.first_seen = now.clone();
service.last_seen = now;
service.status = ASSET_ACTIVE.to_string();
service
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Finding {
pub stable_id: String,
pub asset_id: String,
pub service_id: Option<String>,
pub title: String,
pub severity: String,
pub tool: String,
pub target_url: Option<String>,
pub detail: Option<String>,
pub reference: Option<String>,
#[serde(default)]
pub cves: Vec<String>,
#[serde(default)]
pub endpoints: Vec<String>,
#[serde(default)]
pub technologies: Vec<String>,
pub first_seen: String,
pub last_seen: String,
pub status: String,
pub exploitability: Option<String>,
#[serde(default)]
pub metadata: Value,
}
impl Finding {
pub fn new(asset_id: &str, tool: &str, title: &str) -> Self {
let key = format!("{}:{}:{}", asset_id, tool, title);
let mut finding = Self {
stable_id: stable_id("finding", &key),
asset_id: asset_id.to_string(),
tool: tool.to_string(),
title: title.to_string(),
..Default::default()
};
finding.metadata = Value::Object(Default::default());
let now = now_utc();
finding.first_seen = now.clone();
finding.last_seen = now;
finding.status = ASSET_ACTIVE.to_string();
finding
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Observation {
pub stable_id: String,
pub run_id: String,
pub subject_type: String,
pub subject_id: String,
pub kind: String,
#[serde(default)]
pub before: Value,
#[serde(default)]
pub after: Value,
pub detail: String,
pub observed_at: String,
}
impl Observation {
pub fn new(
run_id: &str,
subject_type: &str,
subject_id: &str,
kind: &str,
before: Value,
after: Value,
detail: &str,
) -> Self {
let before_s = before.to_string();
let after_s = after.to_string();
let key = format!(
"{}:{}:{}:{}:{}:{}",
run_id, subject_type, subject_id, kind, before_s, after_s
);
Self {
stable_id: stable_id("observation", &key),
run_id: run_id.to_string(),
subject_type: subject_type.to_string(),
subject_id: subject_id.to_string(),
kind: kind.to_string(),
before,
after,
detail: detail.to_string(),
observed_at: now_utc(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Relationship {
pub stable_id: String,
pub run_id: String,
pub subject_type: String,
pub subject_id: String,
pub object_type: String,
pub object_id: String,
pub kind: String,
pub detail: String,
}
impl Relationship {
pub fn new(
run_id: &str,
subject_type: &str,
subject_id: &str,
object_type: &str,
object_id: &str,
kind: &str,
detail: &str,
) -> Self {
let key = format!(
"{}:{}:{}:{}:{}",
subject_type, subject_id, object_type, object_id, kind
);
Self {
stable_id: stable_id("relationship", &key),
run_id: run_id.to_string(),
subject_type: subject_type.to_string(),
subject_id: subject_id.to_string(),
object_type: object_type.to_string(),
object_id: object_id.to_string(),
kind: kind.to_string(),
detail: detail.to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ScanRun {
pub stable_id: String,
pub target: String,
pub folder: String,
pub started_at: Option<String>,
pub finished_at: Option<String>,
#[serde(default)]
pub tools: Vec<String>,
#[serde(default)]
pub artifacts: Vec<Value>,
pub content_hash: String,
pub imported_at: String,
#[serde(default)]
pub stats: Value,
}
impl ScanRun {
pub fn new(
run_id: &str,
target: &str,
folder: &str,
content_hash: &str,
started_at: Option<String>,
) -> Self {
Self {
stable_id: run_id.to_string(),
target: target.to_string(),
folder: folder.to_string(),
started_at,
finished_at: None,
content_hash: content_hash.to_string(),
imported_at: now_utc(),
stats: Value::Object(Default::default()),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Report {
pub stable_id: String,
pub run_id: String,
pub tool: String,
pub format: String,
pub title: String,
pub source_path: String,
pub content: String,
pub content_hash: String,
pub imported_at: String,
}
impl Report {
pub fn new(
run_id: &str,
tool: &str,
format: &str,
title: &str,
source_path: &str,
content: &str,
content_hash: &str,
) -> Self {
let key = format!("{}:{}:{}:{}", run_id, tool, format, content_hash);
Self {
stable_id: stable_id("report", &key),
run_id: run_id.to_string(),
tool: tool.to_string(),
format: format.to_string(),
title: title.to_string(),
source_path: source_path.to_string(),
content: content.to_string(),
content_hash: content_hash.to_string(),
imported_at: now_utc(),
}
}
}