1pub mod assessment;
40pub mod config;
41pub mod constants;
42pub mod feeds;
43pub mod sources;
44pub mod types;
45
46pub use assessment::*;
47pub use config::*;
48pub use constants::*;
49pub use feeds::*;
50pub use sources::*;
51pub use types::*;
52
53use anyhow::Result;
54use chrono::{DateTime, Utc};
55use std::collections::HashMap;
56
57#[cfg(feature = "tracing")]
59use tracing::{error, info, warn};
60
61#[cfg(not(feature = "tracing"))]
62macro_rules! info {
63 ($($arg:tt)*) => {};
64}
65
66#[cfg(not(feature = "tracing"))]
67macro_rules! warn {
68 ($($arg:tt)*) => {
69 eprintln!("WARN: {}", format!($($arg)*));
70 };
71}
72
73pub struct ThreatIntelEngine {
75 config: ThreatIntelConfig,
76 sources: HashMap<String, Box<dyn ThreatSource>>,
77 last_sync: Option<DateTime<Utc>>,
78 cache: ThreatCache,
79}
80
81impl ThreatIntelEngine {
82 pub fn new(config: ThreatIntelConfig) -> Self {
84 Self {
85 config,
86 sources: HashMap::new(),
87 last_sync: None,
88 cache: ThreatCache::new(),
89 }
90 }
91
92 pub async fn initialize(&mut self) -> Result<()> {
94 info!("Initializing threat intelligence engine...");
95
96 for source_config in self.config.get_enabled_sources() {
97 info!("Initializing source: {}", source_config.name);
98
99 match self.create_source(source_config).await {
100 Ok(source) => {
101 self.sources.insert(source_config.id.clone(), source);
102 }
103 Err(e) => {
104 warn!("Failed to initialize source {}: {}", source_config.name, e);
105 }
107 }
108 }
109
110 info!(
111 "Threat intelligence engine initialized with {} sources",
112 self.sources.len()
113 );
114
115 self.sync().await?;
117
118 Ok(())
119 }
120
121 pub async fn sync(&mut self) -> Result<()> {
123 info!("Syncing threat intelligence sources...");
124
125 for (id, source) in &mut self.sources {
126 match source.fetch().await {
127 Ok(data) => {
128 info!("Successfully synced source: {}", id);
129 self.cache.update(id, data);
130 }
131 Err(e) => {
132 warn!("Failed to sync source {}: {}", id, e);
133 }
135 }
136 }
137
138 self.last_sync = Some(Utc::now());
139 Ok(())
140 }
141
142 pub async fn query_vulnerabilities(
144 &self,
145 product: &str,
146 version: &str,
147 ) -> Result<Vec<Vulnerability>> {
148 let sources = self
149 .config
150 .get_sources_by_capability(SourceCapability::Vulnerabilities);
151
152 let mut results = Vec::new();
153
154 for source_config in sources {
155 if let Some(data) = self.cache.get(&source_config.id) {
156 let vulns = data.vulnerabilities.iter()
157 .filter(|v| {
158 v.affected_products.iter().any(|p| {
159 p.product.to_lowercase().contains(&product.to_lowercase())
160 && p.version.contains(version)
161 })
162 })
163 .cloned()
164 .collect::<Vec<_>>();
165
166 results.extend(vulns);
167 }
168 }
169
170 Ok(results)
171 }
172
173 pub async fn query_iocs(&self, ioc_type: IOCType) -> Result<Vec<IOC>> {
175 let sources = self
176 .config
177 .get_sources_by_capability(SourceCapability::Ioc);
178
179 let mut results = Vec::new();
180
181 for source_config in sources {
182 if let Some(data) = self.cache.get(&source_config.id) {
183 let iocs = data.iocs.iter()
184 .filter(|ioc| ioc.ioc_type == ioc_type)
185 .cloned()
186 .collect::<Vec<_>>();
187
188 results.extend(iocs);
189 }
190 }
191
192 Ok(results)
193 }
194
195 pub async fn query_threat_actors(&self, query: &str) -> Result<Vec<ThreatActor>> {
197 let sources = self
198 .config
199 .get_sources_by_capability(SourceCapability::ThreatActors);
200
201 let mut results = Vec::new();
202 let query_lower = query.to_lowercase();
203
204 for source_config in sources {
205 if let Some(data) = self.cache.get(&source_config.id) {
206 let actors = data.threat_actors.iter()
207 .filter(|actor| {
208 actor.name.to_lowercase().contains(&query_lower)
209 || actor.aliases.iter().any(|a| a.to_lowercase().contains(&query_lower))
210 })
211 .cloned()
212 .collect::<Vec<_>>();
213
214 results.extend(actors);
215 }
216 }
217
218 Ok(results)
219 }
220
221 pub fn assess_risk(&self, vulnerabilities: &[Vulnerability]) -> RiskAssessment {
223 assessment::assess_risk(vulnerabilities)
224 }
225
226 pub fn get_stats(&self) -> ThreatIntelStats {
228 let mut total_vulnerabilities = 0;
229 let mut total_iocs = 0;
230 let mut total_threat_actors = 0;
231
232 for data in self.cache.cache.values() {
233 total_vulnerabilities += data.vulnerabilities.len();
234 total_iocs += data.iocs.len();
235 total_threat_actors += data.threat_actors.len();
236 }
237
238 ThreatIntelStats {
239 sources_count: self.sources.len(),
240 total_vulnerabilities,
241 total_iocs,
242 total_threat_actors,
243 last_sync: self.last_sync,
244 }
245 }
246
247 async fn create_source(&self, config: &SourceConfig) -> Result<Box<dyn ThreatSource>> {
249 match config.source_type {
250 SourceType::MitreAttack => {
251 Ok(Box::new(sources::MitreAttackSource::new(config.clone())))
252 }
253 SourceType::Cve => {
254 Ok(Box::new(sources::CVESource::new(config.clone())))
255 }
256 SourceType::Osint => {
257 Ok(Box::new(sources::OSINTSource::new(config.clone())))
258 }
259 SourceType::Commercial | SourceType::Custom => {
260 Ok(Box::new(sources::GenericSource::new(config.clone())))
261 }
262 }
263 }
264}
265
266struct ThreatCache {
268 cache: HashMap<String, ThreatData>,
269}
270
271impl ThreatCache {
272 fn new() -> Self {
273 Self {
274 cache: HashMap::new(),
275 }
276 }
277
278 fn update(&mut self, source_id: &str, data: ThreatData) {
279 self.cache.insert(source_id.to_string(), data);
280 }
281
282 fn get(&self, source_id: &str) -> Option<&ThreatData> {
283 self.cache.get(source_id)
284 }
285}