WIP: allow using nff as a diagnostics source #1
					 2 changed files with 122 additions and 15 deletions
				
			
		diagnostic: update configuration options to use clap's ArgAction
		
	
				commit
				
					
					
						0eaa21a3ff
					
				
			
		|  | @ -607,6 +607,11 @@ impl AnalyzerModule for StyleAnalyzer { | ||||||
|     fn analyze(&self, source: &str, config: &DiagnosticConfig) -> Vec<Diagnostic> { |     fn analyze(&self, source: &str, config: &DiagnosticConfig) -> Vec<Diagnostic> { | ||||||
|         let mut diagnostics = Vec::new(); |         let mut diagnostics = Vec::new(); | ||||||
| 
 | 
 | ||||||
|  |         // Only perform style analysis if enabled
 | ||||||
|  |         if !config.enable_style_warnings { | ||||||
|  |             return diagnostics; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // Check for missing shebang
 |         // Check for missing shebang
 | ||||||
|         if !source.starts_with("#!") { |         if !source.starts_with("#!") { | ||||||
|             let range = Range::new(Position::new(0, 0), Position::new(0, 0)); |             let range = Range::new(Position::new(0, 0), Position::new(0, 0)); | ||||||
|  | @ -802,14 +807,29 @@ impl StyleAnalyzer { | ||||||
| pub struct SemanticAnalyzer; | pub struct SemanticAnalyzer; | ||||||
| 
 | 
 | ||||||
| impl AnalyzerModule for SemanticAnalyzer { | impl AnalyzerModule for SemanticAnalyzer { | ||||||
|     fn analyze(&self, source: &str, _config: &DiagnosticConfig) -> Vec<Diagnostic> { |     fn analyze(&self, source: &str, config: &DiagnosticConfig) -> Vec<Diagnostic> { | ||||||
|         let mut diagnostics = Vec::new(); |         let mut diagnostics = Vec::new(); | ||||||
| 
 | 
 | ||||||
|         // Parse and validate nftables-specific constructs
 |         // Always run semantic validation (syntax/semantic errors)
 | ||||||
|         diagnostics.extend(self.validate_table_declarations(source)); |         diagnostics.extend(self.validate_table_declarations(source)); | ||||||
|         diagnostics.extend(self.validate_chain_declarations(source)); |         diagnostics.extend(self.validate_chain_declarations_semantic(source)); | ||||||
|         diagnostics.extend(self.validate_cidr_notation(source)); |         diagnostics.extend(self.validate_cidr_notation(source)); | ||||||
|  | 
 | ||||||
|  |         // Best practices checks (only if enabled)
 | ||||||
|  |         if config.enable_best_practices { | ||||||
|  |             diagnostics.extend(self.validate_chain_best_practices(source)); | ||||||
|             diagnostics.extend(self.check_for_redundant_rules(source)); |             diagnostics.extend(self.check_for_redundant_rules(source)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Performance hints (only if enabled)
 | ||||||
|  |         if config.enable_performance_hints { | ||||||
|  |             diagnostics.extend(self.check_performance_hints(source)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Security warnings (only if enabled)
 | ||||||
|  |         if config.enable_security_warnings { | ||||||
|  |             diagnostics.extend(self.check_security_warnings(source)); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         diagnostics |         diagnostics | ||||||
|     } |     } | ||||||
|  | @ -880,7 +900,7 @@ impl SemanticAnalyzer { | ||||||
|         diagnostics |         diagnostics | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn validate_chain_declarations(&self, source: &str) -> Vec<Diagnostic> { |     fn validate_chain_declarations_semantic(&self, source: &str) -> Vec<Diagnostic> { | ||||||
|         let mut diagnostics = Vec::new(); |         let mut diagnostics = Vec::new(); | ||||||
| 
 | 
 | ||||||
|         for (line_idx, line) in source.lines().enumerate() { |         for (line_idx, line) in source.lines().enumerate() { | ||||||
|  | @ -888,7 +908,7 @@ impl SemanticAnalyzer { | ||||||
|             let trimmed = line.trim(); |             let trimmed = line.trim(); | ||||||
| 
 | 
 | ||||||
|             if trimmed.starts_with("type ") && trimmed.contains("hook") { |             if trimmed.starts_with("type ") && trimmed.contains("hook") { | ||||||
|                 // Validate chain type and hook
 |                 // Validate chain type and hook (semantic validation)
 | ||||||
|                 if let Some(hook_pos) = trimmed.find("hook") { |                 if let Some(hook_pos) = trimmed.find("hook") { | ||||||
|                     let hook_part = &trimmed[hook_pos..]; |                     let hook_part = &trimmed[hook_pos..]; | ||||||
|                     let hook_words: Vec<&str> = hook_part.split_whitespace().collect(); |                     let hook_words: Vec<&str> = hook_part.split_whitespace().collect(); | ||||||
|  | @ -916,8 +936,20 @@ impl SemanticAnalyzer { | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|                 // Check for missing policy in filter chains
 |         diagnostics | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn validate_chain_best_practices(&self, source: &str) -> Vec<Diagnostic> { | ||||||
|  |         let mut diagnostics = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         for (line_idx, line) in source.lines().enumerate() { | ||||||
|  |             let line_num = line_idx as u32; | ||||||
|  |             let trimmed = line.trim(); | ||||||
|  | 
 | ||||||
|  |             // Check for missing policy in filter chains (best practice)
 | ||||||
|             if trimmed.contains("type filter") && !trimmed.contains("policy") { |             if trimmed.contains("type filter") && !trimmed.contains("policy") { | ||||||
|                 let range = Range::new( |                 let range = Range::new( | ||||||
|                     Position::new(line_num, 0), |                     Position::new(line_num, 0), | ||||||
|  | @ -932,6 +964,81 @@ impl SemanticAnalyzer { | ||||||
|                 diagnostics.push(diagnostic); |                 diagnostics.push(diagnostic); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         diagnostics | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn check_performance_hints(&self, source: &str) -> Vec<Diagnostic> { | ||||||
|  |         let mut diagnostics = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         for (line_idx, line) in source.lines().enumerate() { | ||||||
|  |             let line_num = line_idx as u32; | ||||||
|  |             let trimmed = line.trim(); | ||||||
|  | 
 | ||||||
|  |             // Check for large sets without timeout (performance hint)
 | ||||||
|  |             if trimmed.contains("set ") && trimmed.contains("{") && !trimmed.contains("timeout") { | ||||||
|  |                 // Simple heuristic: if set definition is long, suggest timeout
 | ||||||
|  |                 if trimmed.len() > 100 { | ||||||
|  |                     let range = Range::new( | ||||||
|  |                         Position::new(line_num, 0), | ||||||
|  |                         Position::new(line_num, line.len() as u32), | ||||||
|  |                     ); | ||||||
|  |                     let diagnostic = Diagnostic::new( | ||||||
|  |                         range, | ||||||
|  |                         DiagnosticSeverity::Hint, | ||||||
|  |                         DiagnosticCode::LargeSetWithoutTimeout, | ||||||
|  |                         "Consider adding a timeout to large sets for better performance" | ||||||
|  |                             .to_string(), | ||||||
|  |                     ); | ||||||
|  |                     diagnostics.push(diagnostic); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Check for missing counters (performance hint)
 | ||||||
|  |             if (trimmed.contains(" accept") || trimmed.contains(" drop")) | ||||||
|  |                 && !trimmed.contains("counter") | ||||||
|  |             { | ||||||
|  |                 let range = Range::new( | ||||||
|  |                     Position::new(line_num, 0), | ||||||
|  |                     Position::new(line_num, line.len() as u32), | ||||||
|  |                 ); | ||||||
|  |                 let diagnostic = Diagnostic::new( | ||||||
|  |                     range, | ||||||
|  |                     DiagnosticSeverity::Hint, | ||||||
|  |                     DiagnosticCode::MissingCounters, | ||||||
|  |                     "Consider adding counters to rules for monitoring and debugging".to_string(), | ||||||
|  |                 ); | ||||||
|  |                 diagnostics.push(diagnostic); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         diagnostics | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn check_security_warnings(&self, source: &str) -> Vec<Diagnostic> { | ||||||
|  |         let mut diagnostics = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         for (line_idx, line) in source.lines().enumerate() { | ||||||
|  |             let line_num = line_idx as u32; | ||||||
|  |             let trimmed = line.trim(); | ||||||
|  | 
 | ||||||
|  |             // Check for overly permissive rules (security warning)
 | ||||||
|  |             if trimmed.contains(" accept") | ||||||
|  |                 && (trimmed.contains("0.0.0.0/0") || trimmed.contains("::/0")) | ||||||
|  |             { | ||||||
|  |                 let range = Range::new( | ||||||
|  |                     Position::new(line_num, 0), | ||||||
|  |                     Position::new(line_num, line.len() as u32), | ||||||
|  |                 ); | ||||||
|  |                 let diagnostic = Diagnostic::new( | ||||||
|  |                     range, | ||||||
|  |                     DiagnosticSeverity::Warning, | ||||||
|  |                     DiagnosticCode::SecurityRisk, | ||||||
|  |                     "Rule accepts traffic from anywhere - consider restricting source addresses" | ||||||
|  |                         .to_string(), | ||||||
|  |                 ); | ||||||
|  |                 diagnostics.push(diagnostic); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         diagnostics |         diagnostics | ||||||
|  |  | ||||||
|  | @ -93,19 +93,19 @@ enum Commands { | ||||||
|         json: bool, |         json: bool, | ||||||
| 
 | 
 | ||||||
|         /// Include style warnings in diagnostics
 |         /// Include style warnings in diagnostics
 | ||||||
|         #[arg(long, default_value = "true")] |         #[arg(long, action = clap::ArgAction::Set, default_value = "true")] | ||||||
|         style_warnings: bool, |         style_warnings: bool, | ||||||
| 
 | 
 | ||||||
|         /// Include best practice recommendations in diagnostics
 |         /// Include best practice recommendations in diagnostics
 | ||||||
|         #[arg(long, default_value = "true")] |         #[arg(long, action = clap::ArgAction::Set, default_value = "true")] | ||||||
|         best_practices: bool, |         best_practices: bool, | ||||||
| 
 | 
 | ||||||
|         /// Include performance hints in diagnostics
 |         /// Include performance hints in diagnostics
 | ||||||
|         #[arg(long, default_value = "true")] |         #[arg(long, action = clap::ArgAction::Set, default_value = "true")] | ||||||
|         performance_hints: bool, |         performance_hints: bool, | ||||||
| 
 | 
 | ||||||
|         /// Include security warnings in diagnostics
 |         /// Include security warnings in diagnostics
 | ||||||
|         #[arg(long, default_value = "true")] |         #[arg(long, action = clap::ArgAction::Set, default_value = "true")] | ||||||
|         security_warnings: bool, |         security_warnings: bool, | ||||||
| 
 | 
 | ||||||
|         /// Diagnostic modules to run (comma-separated: lexical,syntax,style,semantic)
 |         /// Diagnostic modules to run (comma-separated: lexical,syntax,style,semantic)
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue