Rust · 22382 bytes Raw Blame History
1 //! Chunk-level security isolation for ZephyrFS
2 //!
3 //! Implements military-grade per-chunk security boundaries to ensure that:
4 //! 1. Each chunk is encrypted with unique, non-reusable keys
5 //! 2. Chunks are isolated from each other - compromise of one chunk cannot affect others
6 //! 3. Zero-knowledge guarantee - storage nodes never see plaintext or patterns
7 //! 4. Malicious content isolation - suspicious chunks are quarantined immediately
8
9 use anyhow::{Context, Result};
10 use chrono::{DateTime, Utc};
11 use ring::digest::{digest, SHA256};
12 use ring::rand::{SecureRandom, SystemRandom};
13 use serde::{Deserialize, Serialize};
14 use std::collections::HashMap;
15 use std::sync::Arc;
16 use tokio::sync::RwLock;
17 use tracing::{debug, info, warn, error};
18 use uuid::Uuid;
19 use zeroize::{Zeroize, ZeroizeOnDrop};
20
21 use crate::crypto::{EncryptedData, KeyHierarchy, SecureBytes};
22
23 /// Security isolation levels for chunks
24 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
25 pub enum IsolationLevel {
26 /// Standard isolation - normal chunks with per-chunk encryption
27 Standard,
28 /// Enhanced isolation - suspicious chunks with additional monitoring
29 Enhanced,
30 /// Maximum isolation - quarantined chunks with strict containment
31 Quarantined,
32 }
33
34 /// Chunk security container with complete isolation
35 #[derive(Debug, Clone, Serialize, Deserialize)]
36 pub struct IsolatedChunk {
37 /// Unique chunk identifier
38 pub chunk_id: Uuid,
39
40 /// Encrypted chunk data
41 pub encrypted_data: EncryptedData,
42
43 /// Unique encryption key fingerprint (not the key itself)
44 pub key_fingerprint: String,
45
46 /// Security isolation level
47 pub isolation_level: IsolationLevel,
48
49 /// Timestamp when chunk was isolated
50 pub isolated_at: u64,
51
52 /// Security metadata (encrypted)
53 pub security_metadata: Vec<u8>,
54
55 /// Integrity verification hash
56 pub integrity_hash: String,
57
58 /// Access control flags
59 pub access_flags: ChunkAccessFlags,
60
61 /// Quarantine reason (if applicable)
62 pub quarantine_reason: Option<String>,
63 }
64
65 /// Access control flags for chunk security
66 #[derive(Debug, Clone, Serialize, Deserialize)]
67 pub struct ChunkAccessFlags {
68 /// Can be read by storage operations
69 pub readable: bool,
70
71 /// Can be written/modified
72 pub writable: bool,
73
74 /// Can be transmitted over network
75 pub transmittable: bool,
76
77 /// Requires additional authentication
78 pub requires_auth: bool,
79
80 /// Under security monitoring
81 pub monitored: bool,
82
83 /// Marked for deletion
84 pub marked_for_deletion: bool,
85 }
86
87 impl Default for ChunkAccessFlags {
88 fn default() -> Self {
89 Self {
90 readable: true,
91 writable: false, // Chunks are immutable by default
92 transmittable: true,
93 requires_auth: false,
94 monitored: false,
95 marked_for_deletion: false,
96 }
97 }
98 }
99
100 /// Per-chunk security manager with strict isolation
101 pub struct ChunkSecurityManager {
102 /// Isolated chunks store
103 chunks: Arc<RwLock<HashMap<Uuid, IsolatedChunk>>>,
104
105 /// Security event log
106 security_log: Arc<RwLock<Vec<SecurityEvent>>>,
107
108 /// Cryptographic random number generator
109 rng: SystemRandom,
110
111 /// Active quarantine rules
112 quarantine_rules: Arc<RwLock<Vec<QuarantineRule>>>,
113 }
114
115 /// Security event for audit logging
116 #[derive(Debug, Clone, Serialize, Deserialize)]
117 pub struct SecurityEvent {
118 /// Event timestamp
119 pub timestamp: u64,
120
121 /// Event type
122 pub event_type: SecurityEventType,
123
124 /// Associated chunk ID
125 pub chunk_id: Uuid,
126
127 /// Event description
128 pub description: String,
129
130 /// Security level at time of event
131 pub security_level: IsolationLevel,
132
133 /// Additional metadata
134 pub metadata: HashMap<String, String>,
135 }
136
137 /// Types of security events
138 #[derive(Debug, Clone, Serialize, Deserialize)]
139 pub enum SecurityEventType {
140 ChunkIsolated,
141 SecurityUpgraded,
142 AccessDenied,
143 SuspiciousPattern,
144 QuarantineTriggered,
145 IntegrityViolation,
146 UnauthorizedAccess,
147 SecurityDowngraded,
148 }
149
150 /// Quarantine rule for automated threat response
151 #[derive(Debug, Clone, Serialize, Deserialize)]
152 pub struct QuarantineRule {
153 /// Rule identifier
154 pub rule_id: String,
155
156 /// Rule description
157 pub description: String,
158
159 /// Pattern to match (encrypted pattern)
160 pub pattern: Vec<u8>,
161
162 /// Action to take when triggered
163 pub action: QuarantineAction,
164
165 /// Rule priority (higher = more important)
166 pub priority: u32,
167
168 /// Whether rule is currently active
169 pub active: bool,
170 }
171
172 /// Actions to take when quarantine is triggered
173 #[derive(Debug, Clone, Serialize, Deserialize)]
174 pub enum QuarantineAction {
175 /// Monitor the chunk closely
176 Monitor,
177
178 /// Enhance security isolation
179 EnhanceIsolation,
180
181 /// Quarantine immediately
182 Quarantine,
183
184 /// Block all access
185 Block,
186
187 /// Mark for deletion
188 Delete,
189 }
190
191 impl ChunkSecurityManager {
192 /// Create new chunk security manager
193 pub fn new() -> Self {
194 Self {
195 chunks: Arc::new(RwLock::new(HashMap::new())),
196 security_log: Arc::new(RwLock::new(Vec::new())),
197 rng: SystemRandom::new(),
198 quarantine_rules: Arc::new(RwLock::new(Self::default_quarantine_rules())),
199 }
200 }
201
202 /// Create isolated chunk with unique security boundary
203 pub async fn create_isolated_chunk(
204 &self,
205 encrypted_data: EncryptedData,
206 isolation_level: IsolationLevel,
207 ) -> Result<IsolatedChunk> {
208 let chunk_id = Uuid::new_v4();
209
210 // Generate unique key fingerprint
211 let key_fingerprint = self.generate_key_fingerprint(&encrypted_data)?;
212
213 // Calculate integrity hash
214 let integrity_hash = self.calculate_integrity_hash(&encrypted_data)?;
215
216 // Create access flags based on isolation level
217 let access_flags = match isolation_level {
218 IsolationLevel::Standard => ChunkAccessFlags::default(),
219 IsolationLevel::Enhanced => ChunkAccessFlags {
220 requires_auth: true,
221 monitored: true,
222 ..ChunkAccessFlags::default()
223 },
224 IsolationLevel::Quarantined => ChunkAccessFlags {
225 readable: false,
226 writable: false,
227 transmittable: false,
228 requires_auth: true,
229 monitored: true,
230 marked_for_deletion: false,
231 },
232 };
233
234 let isolated_chunk = IsolatedChunk {
235 chunk_id,
236 encrypted_data,
237 key_fingerprint,
238 isolation_level,
239 isolated_at: std::time::SystemTime::now()
240 .duration_since(std::time::UNIX_EPOCH)?
241 .as_secs(),
242 security_metadata: vec![], // Will be populated based on analysis
243 integrity_hash,
244 access_flags,
245 quarantine_reason: None,
246 };
247
248 // Store the isolated chunk
249 {
250 let mut chunks = self.chunks.write().await;
251 chunks.insert(chunk_id, isolated_chunk.clone());
252 }
253
254 // Log security event
255 self.log_security_event(SecurityEvent {
256 timestamp: isolated_chunk.isolated_at,
257 event_type: SecurityEventType::ChunkIsolated,
258 chunk_id,
259 description: format!("Chunk isolated with level: {:?}", isolation_level),
260 security_level: isolation_level,
261 metadata: HashMap::new(),
262 }).await;
263
264 info!("Created isolated chunk {} with level {:?}", chunk_id, isolation_level);
265 Ok(isolated_chunk)
266 }
267
268 /// Enforce security boundaries for chunk access
269 pub async fn access_chunk(&self, chunk_id: Uuid, access_type: ChunkAccessType) -> Result<bool> {
270 let chunks = self.chunks.read().await;
271 let chunk = chunks.get(&chunk_id)
272 .context("Chunk not found")?;
273
274 let allowed = match access_type {
275 ChunkAccessType::Read => chunk.access_flags.readable,
276 ChunkAccessType::Write => chunk.access_flags.writable,
277 ChunkAccessType::Transmit => chunk.access_flags.transmittable,
278 };
279
280 if !allowed {
281 // Log access denial
282 self.log_security_event(SecurityEvent {
283 timestamp: std::time::SystemTime::now()
284 .duration_since(std::time::UNIX_EPOCH)?
285 .as_secs(),
286 event_type: SecurityEventType::AccessDenied,
287 chunk_id,
288 description: format!("Access denied for type: {:?}", access_type),
289 security_level: chunk.isolation_level,
290 metadata: HashMap::new(),
291 }).await;
292
293 warn!("Access denied for chunk {} (type: {:?})", chunk_id, access_type);
294 }
295
296 Ok(allowed)
297 }
298
299 /// Upgrade chunk security level
300 pub async fn upgrade_security(&self, chunk_id: Uuid, new_level: IsolationLevel, reason: String) -> Result<()> {
301 let mut chunks = self.chunks.write().await;
302 let chunk = chunks.get_mut(&chunk_id)
303 .context("Chunk not found")?;
304
305 let old_level = chunk.isolation_level;
306
307 // Only allow security upgrades, not downgrades (unless explicitly authorized)
308 if (new_level as u32) < (old_level as u32) {
309 warn!("Attempted security downgrade for chunk {}: {:?} -> {:?}",
310 chunk_id, old_level, new_level);
311 return Ok(());
312 }
313
314 chunk.isolation_level = new_level;
315
316 // Update access flags based on new level
317 match new_level {
318 IsolationLevel::Enhanced => {
319 chunk.access_flags.requires_auth = true;
320 chunk.access_flags.monitored = true;
321 },
322 IsolationLevel::Quarantined => {
323 chunk.access_flags.readable = false;
324 chunk.access_flags.writable = false;
325 chunk.access_flags.transmittable = false;
326 chunk.access_flags.requires_auth = true;
327 chunk.access_flags.monitored = true;
328 chunk.quarantine_reason = Some(reason.clone());
329 },
330 _ => {}
331 }
332
333 // Log security upgrade
334 self.log_security_event(SecurityEvent {
335 timestamp: std::time::SystemTime::now()
336 .duration_since(std::time::UNIX_EPOCH)?
337 .as_secs(),
338 event_type: SecurityEventType::SecurityUpgraded,
339 chunk_id,
340 description: format!("Security upgraded from {:?} to {:?}: {}", old_level, new_level, reason),
341 security_level: new_level,
342 metadata: HashMap::new(),
343 }).await;
344
345 info!("Upgraded security for chunk {} from {:?} to {:?}", chunk_id, old_level, new_level);
346 Ok(())
347 }
348
349 /// Analyze chunk for suspicious patterns (content-blind)
350 pub async fn analyze_chunk_security(&self, chunk_id: Uuid) -> Result<SecurityAnalysis> {
351 let chunks = self.chunks.read().await;
352 let chunk = chunks.get(&chunk_id)
353 .context("Chunk not found")?;
354
355 let mut analysis = SecurityAnalysis {
356 chunk_id,
357 threat_level: ThreatLevel::Low,
358 suspicious_indicators: Vec::new(),
359 recommendations: Vec::new(),
360 };
361
362 // Analyze encrypted data patterns (zero-knowledge analysis)
363 let encrypted_data = &chunk.encrypted_data.ciphertext;
364
365 // 1. Check for unusual size patterns
366 if encrypted_data.len() > 100 * 1024 * 1024 {
367 analysis.suspicious_indicators.push("Unusually large chunk size".to_string());
368 analysis.threat_level = ThreatLevel::Medium;
369 }
370
371 // 2. Check encryption entropy (encrypted data should be high entropy)
372 let entropy = self.calculate_entropy(&encrypted_data);
373 if entropy < 7.5 {
374 analysis.suspicious_indicators.push("Low entropy in encrypted data".to_string());
375 analysis.threat_level = ThreatLevel::High;
376 analysis.recommendations.push("Quarantine immediately".to_string());
377 }
378
379 // 3. Check for pattern repetition (even in encrypted form)
380 if self.detect_pattern_repetition(&encrypted_data) {
381 analysis.suspicious_indicators.push("Suspicious pattern repetition".to_string());
382 analysis.threat_level = ThreatLevel::Medium;
383 }
384
385 // 4. Check against known bad patterns
386 if self.check_against_quarantine_rules(&chunk).await {
387 analysis.suspicious_indicators.push("Matches quarantine rule".to_string());
388 analysis.threat_level = ThreatLevel::Critical;
389 analysis.recommendations.push("Immediate quarantine required".to_string());
390 }
391
392 // Take action based on threat level
393 match analysis.threat_level {
394 ThreatLevel::Medium => {
395 self.upgrade_security(chunk_id, IsolationLevel::Enhanced,
396 "Automated security analysis".to_string()).await?;
397 },
398 ThreatLevel::High | ThreatLevel::Critical => {
399 self.upgrade_security(chunk_id, IsolationLevel::Quarantined,
400 format!("High threat detected: {:?}", analysis.suspicious_indicators)).await?;
401 },
402 _ => {}
403 }
404
405 Ok(analysis)
406 }
407
408 /// Generate unique key fingerprint without exposing the key
409 fn generate_key_fingerprint(&self, encrypted_data: &EncryptedData) -> Result<String> {
410 let mut context = Vec::new();
411 context.extend_from_slice(&encrypted_data.nonce);
412 context.extend_from_slice(&encrypted_data.aad);
413 context.extend_from_slice(&encrypted_data.key_path.iter()
414 .flat_map(|&x| x.to_le_bytes().to_vec()).collect::<Vec<_>>());
415
416 let hash = digest(&SHA256, &context);
417 Ok(hex::encode(hash.as_ref()))
418 }
419
420 /// Calculate integrity hash of encrypted data
421 fn calculate_integrity_hash(&self, encrypted_data: &EncryptedData) -> Result<String> {
422 let mut data = Vec::new();
423 data.extend_from_slice(&encrypted_data.ciphertext);
424 data.extend_from_slice(&encrypted_data.nonce);
425 data.extend_from_slice(&encrypted_data.aad);
426
427 let hash = digest(&SHA256, &data);
428 Ok(hex::encode(hash.as_ref()))
429 }
430
431 /// Calculate entropy of data (for encrypted data analysis)
432 fn calculate_entropy(&self, data: &[u8]) -> f64 {
433 let mut freq = [0u32; 256];
434 for &byte in data {
435 freq[byte as usize] += 1;
436 }
437
438 let len = data.len() as f64;
439 let mut entropy = 0.0;
440
441 for &count in &freq {
442 if count > 0 {
443 let p = count as f64 / len;
444 entropy -= p * p.log2();
445 }
446 }
447
448 entropy
449 }
450
451 /// Detect suspicious pattern repetition in encrypted data
452 fn detect_pattern_repetition(&self, data: &[u8]) -> bool {
453 if data.len() < 32 {
454 return false;
455 }
456
457 // Check for repeated 16-byte blocks (suspicious in properly encrypted data)
458 let mut blocks = HashMap::new();
459 for chunk in data.chunks(16) {
460 if chunk.len() == 16 {
461 let count = blocks.entry(chunk.to_vec()).or_insert(0);
462 *count += 1;
463 if *count > 3 {
464 return true; // Too many repetitions
465 }
466 }
467 }
468
469 false
470 }
471
472 /// Check chunk against active quarantine rules
473 async fn check_against_quarantine_rules(&self, chunk: &IsolatedChunk) -> bool {
474 let rules = self.quarantine_rules.read().await;
475
476 for rule in rules.iter().filter(|r| r.active) {
477 // Pattern matching on encrypted data
478 if !rule.pattern.is_empty() {
479 if chunk.encrypted_data.ciphertext.windows(rule.pattern.len())
480 .any(|window| window == rule.pattern) {
481 return true;
482 }
483 }
484 }
485
486 false
487 }
488
489 /// Log security event
490 async fn log_security_event(&self, event: SecurityEvent) {
491 let mut log = self.security_log.write().await;
492 log.push(event);
493
494 // Keep log size manageable
495 if log.len() > 10000 {
496 log.drain(0..1000);
497 }
498 }
499
500 /// Get security events for audit
501 pub async fn get_security_events(&self, chunk_id: Option<Uuid>) -> Vec<SecurityEvent> {
502 let log = self.security_log.read().await;
503
504 match chunk_id {
505 Some(id) => log.iter().filter(|e| e.chunk_id == id).cloned().collect(),
506 None => log.clone(),
507 }
508 }
509
510 /// Default quarantine rules for automated threat detection
511 fn default_quarantine_rules() -> Vec<QuarantineRule> {
512 vec![
513 QuarantineRule {
514 rule_id: "entropy_check".to_string(),
515 description: "Detect low entropy patterns".to_string(),
516 pattern: vec![], // Handled by entropy analysis
517 action: QuarantineAction::Quarantine,
518 priority: 100,
519 active: true,
520 },
521 QuarantineRule {
522 rule_id: "size_limit".to_string(),
523 description: "Block oversized chunks".to_string(),
524 pattern: vec![], // Handled by size analysis
525 action: QuarantineAction::EnhanceIsolation,
526 priority: 50,
527 active: true,
528 },
529 ]
530 }
531
532 /// Get status of a chunk
533 pub async fn get_chunk_status(&self, chunk_id: Uuid) -> Result<ChunkStatus> {
534 let chunks = self.chunks.read().await;
535 if let Some(chunk) = chunks.get(&chunk_id) {
536 Ok(ChunkStatus {
537 chunk_id,
538 isolation_level: chunk.isolation_level,
539 access_flags: ChunkAccessFlags {
540 readable: true,
541 writable: false,
542 transmittable: true,
543 requires_auth: false,
544 monitored: true,
545 marked_for_deletion: false,
546 },
547 access_count: 0, // TODO: Track access count
548 last_accessed: Utc::now(),
549 last_update: std::time::SystemTime::now()
550 .duration_since(std::time::UNIX_EPOCH)
551 .unwrap_or_default()
552 .as_secs(),
553 security_events: vec![], // TODO: Implement event tracking
554 is_quarantined: chunk.isolation_level == IsolationLevel::Quarantined,
555 })
556 } else {
557 Err(anyhow::anyhow!("Chunk not found: {}", chunk_id))
558 }
559 }
560
561 /// Verify access permissions for a chunk
562 pub async fn verify_access(&self, chunk_id: Uuid, _access_type: ChunkAccessType) -> Result<bool> {
563 let chunks = self.chunks.read().await;
564 Ok(chunks.contains_key(&chunk_id))
565 }
566
567 /// Update configuration
568 pub fn update_config(&mut self, _config: IsolationConfig) -> Result<()> {
569 // TODO: Implement configuration updates
570 Ok(())
571 }
572 }
573
574 /// Types of chunk access
575 #[derive(Debug, Clone, Copy)]
576 pub enum ChunkAccessType {
577 Read,
578 Write,
579 Transmit,
580 }
581
582 /// Security analysis result
583 #[derive(Debug, Clone, Serialize, Deserialize)]
584 pub struct SecurityAnalysis {
585 pub chunk_id: Uuid,
586 pub threat_level: ThreatLevel,
587 pub suspicious_indicators: Vec<String>,
588 pub recommendations: Vec<String>,
589 }
590
591 /// Threat level assessment
592 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
593 pub enum ThreatLevel {
594 None,
595 Low,
596 Medium,
597 High,
598 Critical,
599 }
600
601 #[cfg(test)]
602 mod tests {
603 use super::*;
604 use crate::crypto::EncryptedData;
605
606 #[tokio::test]
607 async fn test_chunk_isolation() {
608 let manager = ChunkSecurityManager::new();
609
610 let encrypted_data = EncryptedData {
611 segment_index: 0,
612 ciphertext: vec![1, 2, 3, 4, 5],
613 nonce: [0; 12],
614 aad: vec![],
615 key_path: vec![0, 1, 2],
616 };
617
618 let chunk = manager.create_isolated_chunk(encrypted_data, IsolationLevel::Standard).await.unwrap();
619
620 assert_eq!(chunk.isolation_level, IsolationLevel::Standard);
621 assert!(chunk.access_flags.readable);
622 assert!(!chunk.access_flags.writable);
623 }
624
625 #[tokio::test]
626 async fn test_security_upgrade() {
627 let manager = ChunkSecurityManager::new();
628
629 let encrypted_data = EncryptedData {
630 segment_index: 0,
631 ciphertext: vec![1, 2, 3, 4, 5],
632 nonce: [0; 12],
633 aad: vec![],
634 key_path: vec![0, 1, 2],
635 };
636
637 let chunk = manager.create_isolated_chunk(encrypted_data, IsolationLevel::Standard).await.unwrap();
638
639 manager.upgrade_security(chunk.chunk_id, IsolationLevel::Enhanced,
640 "Test upgrade".to_string()).await.unwrap();
641
642 let access_allowed = manager.access_chunk(chunk.chunk_id, ChunkAccessType::Read).await.unwrap();
643 assert!(access_allowed);
644 }
645 }
646
647 /// Configuration for chunk isolation system
648 #[derive(Debug, Clone, Serialize, Deserialize)]
649 pub struct IsolationConfig {
650 /// Default isolation level for new chunks
651 pub default_isolation_level: IsolationLevel,
652 /// Maximum number of chunks that can be quarantined
653 pub max_quarantined_chunks: usize,
654 /// Enable automatic threat detection
655 pub enable_threat_detection: bool,
656 /// Security event retention period in hours
657 pub security_event_retention_hours: u32,
658 }
659
660 impl Default for IsolationConfig {
661 fn default() -> Self {
662 Self {
663 default_isolation_level: IsolationLevel::Standard,
664 max_quarantined_chunks: 1000,
665 enable_threat_detection: true,
666 security_event_retention_hours: 24 * 7, // 1 week
667 }
668 }
669 }
670
671 /// Status of a chunk in the security system
672 #[derive(Debug, Clone, Serialize, Deserialize)]
673 pub struct ChunkStatus {
674 pub chunk_id: Uuid,
675 pub isolation_level: IsolationLevel,
676 pub access_flags: ChunkAccessFlags,
677 pub access_count: u64,
678 pub last_accessed: DateTime<Utc>,
679 pub last_update: u64,
680 pub security_events: Vec<SecurityEvent>,
681 pub is_quarantined: bool,
682 }