🎉 **Architecture Transformation (v2.0)** - Complete migration from web app to professional CLI tool - File-based SIGMA rule management system - Git-friendly directory structure organized by year/CVE-ID - Multiple rule variants per CVE (template, LLM, hybrid) ✨ **New CLI System** - Professional command-line interface with Click framework - 8 command groups: process, generate, search, stats, export, migrate - Modular command architecture for maintainability - Comprehensive help system and configuration management 📁 **File-Based Storage Architecture** - Individual CVE directories: cves/YEAR/CVE-ID/ - Multiple SIGMA rule variants per CVE - JSON metadata with processing history and PoC data - Native YAML files perfect for version control 🚀 **Core CLI Commands** - process: CVE processing and bulk operations - generate: SIGMA rule generation with multiple methods - search: Advanced CVE and rule searching with filters - stats: Comprehensive statistics and analytics - export: Multiple output formats for different workflows - migrate: Database-to-file migration tools 🔧 **Migration Support** - Complete migration utilities from web database - Data validation and integrity checking - Backward compatibility with existing processors - Legacy web interface maintained for transition 📊 **Enhanced Features** - Advanced search with complex filtering (severity, PoC presence, etc.) - Multi-format exports (YAML, JSON, CSV) - Comprehensive statistics and coverage reports - File-based rule versioning and management 🎯 **Production Benefits** - No database dependency - runs anywhere - Perfect for cybersecurity teams using git workflows - Direct integration with SIGMA ecosystems - Portable architecture for CI/CD pipelines - Multiple rule variants for different detection scenarios 📝 **Documentation Updates** - Complete README rewrite for CLI-first approach - Updated CLAUDE.md with new architecture details - Detailed CLI documentation with examples - Migration guides and troubleshooting **Perfect for security teams wanting production-ready SIGMA rules with version control\! 🛡️** 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
313 lines
No EOL
12 KiB
Python
Executable file
313 lines
No EOL
12 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
SIGMA CLI - CVE-SIGMA Auto Generator Command Line Interface
|
|
|
|
A CLI tool for processing CVEs and generating SIGMA detection rules
|
|
in a file-based directory structure.
|
|
|
|
Author: CVE-SIGMA Auto Generator
|
|
"""
|
|
|
|
import click
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
import json
|
|
from typing import Optional, List
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Add parent directories to path for imports
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend'))
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'core'))
|
|
|
|
# Import CLI command modules
|
|
from commands.process_commands import ProcessCommands
|
|
from commands.generate_commands import GenerateCommands
|
|
from commands.search_commands import SearchCommands
|
|
from commands.stats_commands import StatsCommands
|
|
from commands.export_commands import ExportCommands
|
|
from commands.migrate_commands import MigrateCommands
|
|
|
|
# Global CLI configuration
|
|
class Config:
|
|
def __init__(self):
|
|
self.base_dir = Path.cwd()
|
|
self.cves_dir = self.base_dir / "cves"
|
|
self.templates_dir = self.base_dir / "backend" / "templates"
|
|
self.reports_dir = self.base_dir / "reports"
|
|
self.config_file = Path.home() / ".sigma-cli" / "config.yaml"
|
|
|
|
# Ensure directories exist
|
|
self.cves_dir.mkdir(exist_ok=True)
|
|
self.reports_dir.mkdir(exist_ok=True)
|
|
(Path.home() / ".sigma-cli").mkdir(exist_ok=True)
|
|
|
|
pass_config = click.make_pass_decorator(Config, ensure=True)
|
|
|
|
@click.group()
|
|
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
|
|
@click.option('--config', '-c', type=click.Path(), help='Path to configuration file')
|
|
@click.pass_context
|
|
def cli(ctx, verbose, config):
|
|
"""
|
|
SIGMA CLI - CVE-SIGMA Auto Generator
|
|
|
|
A command line tool for processing CVEs and generating SIGMA detection rules.
|
|
Rules are stored in a file-based directory structure organized by year and CVE-ID.
|
|
"""
|
|
ctx.ensure_object(Config)
|
|
if verbose:
|
|
click.echo("Verbose mode enabled")
|
|
|
|
if config:
|
|
ctx.obj.config_file = Path(config)
|
|
|
|
# Initialize logging
|
|
import logging
|
|
level = logging.DEBUG if verbose else logging.INFO
|
|
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
# Process commands
|
|
@cli.group()
|
|
@pass_config
|
|
def process(config):
|
|
"""Process CVEs and generate SIGMA rules"""
|
|
pass
|
|
|
|
@process.command('year')
|
|
@click.argument('year', type=int)
|
|
@click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']),
|
|
default=['template'], help='Rule generation method(s)')
|
|
@click.option('--force', '-f', is_flag=True, help='Force regeneration of existing rules')
|
|
@click.option('--batch-size', '-b', default=50, help='Batch size for processing')
|
|
@pass_config
|
|
def process_year(config, year, method, force, batch_size):
|
|
"""Process all CVEs for a specific year"""
|
|
cmd = ProcessCommands(config)
|
|
asyncio.run(cmd.process_year(year, method, force, batch_size))
|
|
|
|
@process.command('cve')
|
|
@click.argument('cve_id')
|
|
@click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']),
|
|
default=['template'], help='Rule generation method(s)')
|
|
@click.option('--force', '-f', is_flag=True, help='Force regeneration of existing rules')
|
|
@pass_config
|
|
def process_cve(config, cve_id, method, force):
|
|
"""Process a specific CVE"""
|
|
cmd = ProcessCommands(config)
|
|
asyncio.run(cmd.process_cve(cve_id, method, force))
|
|
|
|
@process.command('bulk')
|
|
@click.option('--start-year', default=2022, help='Starting year for bulk processing')
|
|
@click.option('--end-year', default=datetime.now().year, help='Ending year for bulk processing')
|
|
@click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']),
|
|
default=['template'], help='Rule generation method(s)')
|
|
@click.option('--batch-size', '-b', default=50, help='Batch size for processing')
|
|
@pass_config
|
|
def process_bulk(config, start_year, end_year, method, batch_size):
|
|
"""Bulk process all CVEs across multiple years"""
|
|
cmd = ProcessCommands(config)
|
|
asyncio.run(cmd.process_bulk(start_year, end_year, method, batch_size))
|
|
|
|
@process.command('incremental')
|
|
@click.option('--days', '-d', default=7, help='Process CVEs modified in the last N days')
|
|
@click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']),
|
|
default=['template'], help='Rule generation method(s)')
|
|
@pass_config
|
|
def process_incremental(config, days, method):
|
|
"""Process recently modified CVEs"""
|
|
cmd = ProcessCommands(config)
|
|
asyncio.run(cmd.process_incremental(days, method))
|
|
|
|
# Generate commands
|
|
@cli.group()
|
|
@pass_config
|
|
def generate(config):
|
|
"""Generate SIGMA rules for existing CVEs"""
|
|
pass
|
|
|
|
@generate.command('cve')
|
|
@click.argument('cve_id')
|
|
@click.option('--method', '-m', type=click.Choice(['template', 'llm', 'hybrid', 'all']),
|
|
default='template', help='Rule generation method')
|
|
@click.option('--provider', '-p', type=click.Choice(['openai', 'anthropic', 'ollama']),
|
|
help='LLM provider for LLM-based generation')
|
|
@click.option('--model', help='Specific model to use')
|
|
@click.option('--force', '-f', is_flag=True, help='Force regeneration of existing rules')
|
|
@pass_config
|
|
def generate_cve(config, cve_id, method, provider, model, force):
|
|
"""Generate SIGMA rules for a specific CVE"""
|
|
cmd = GenerateCommands(config)
|
|
asyncio.run(cmd.generate_cve(cve_id, method, provider, model, force))
|
|
|
|
@generate.command('regenerate')
|
|
@click.option('--year', type=int, help='Regenerate rules for specific year')
|
|
@click.option('--method', '-m', type=click.Choice(['template', 'llm', 'hybrid', 'all']),
|
|
default='all', help='Rule generation method')
|
|
@click.option('--filter-quality', type=click.Choice(['excellent', 'good', 'fair']),
|
|
help='Only regenerate rules for CVEs with specific PoC quality')
|
|
@pass_config
|
|
def generate_regenerate(config, year, method, filter_quality):
|
|
"""Regenerate existing SIGMA rules"""
|
|
cmd = GenerateCommands(config)
|
|
asyncio.run(cmd.regenerate_rules(year, method, filter_quality))
|
|
|
|
# Search commands
|
|
@cli.group()
|
|
@pass_config
|
|
def search(config):
|
|
"""Search CVEs and SIGMA rules"""
|
|
pass
|
|
|
|
@search.command('cve')
|
|
@click.argument('pattern')
|
|
@click.option('--year', type=int, help='Search within specific year')
|
|
@click.option('--severity', type=click.Choice(['low', 'medium', 'high', 'critical']), help='Filter by severity')
|
|
@click.option('--has-poc', is_flag=True, help='Only show CVEs with PoC data')
|
|
@click.option('--has-rules', is_flag=True, help='Only show CVEs with generated rules')
|
|
@click.option('--limit', '-l', default=20, help='Limit number of results')
|
|
@pass_config
|
|
def search_cve(config, pattern, year, severity, has_poc, has_rules, limit):
|
|
"""Search for CVEs by pattern"""
|
|
cmd = SearchCommands(config)
|
|
asyncio.run(cmd.search_cves(pattern, year, severity, has_poc, has_rules, limit))
|
|
|
|
@search.command('rules')
|
|
@click.argument('pattern')
|
|
@click.option('--rule-type', help='Filter by rule type (e.g., process, network, file)')
|
|
@click.option('--method', type=click.Choice(['template', 'llm', 'hybrid']), help='Filter by generation method')
|
|
@click.option('--limit', '-l', default=20, help='Limit number of results')
|
|
@pass_config
|
|
def search_rules(config, pattern, rule_type, method, limit):
|
|
"""Search for SIGMA rules by pattern"""
|
|
cmd = SearchCommands(config)
|
|
asyncio.run(cmd.search_rules(pattern, rule_type, method, limit))
|
|
|
|
# Statistics commands
|
|
@cli.group()
|
|
@pass_config
|
|
def stats(config):
|
|
"""Generate statistics and reports"""
|
|
pass
|
|
|
|
@stats.command('overview')
|
|
@click.option('--year', type=int, help='Statistics for specific year')
|
|
@click.option('--output', '-o', type=click.Path(), help='Save output to file')
|
|
@pass_config
|
|
def stats_overview(config, year, output):
|
|
"""Generate overview statistics"""
|
|
cmd = StatsCommands(config)
|
|
asyncio.run(cmd.overview(year, output))
|
|
|
|
@stats.command('poc')
|
|
@click.option('--year', type=int, help='PoC statistics for specific year')
|
|
@pass_config
|
|
def stats_poc(config, year):
|
|
"""Generate PoC coverage statistics"""
|
|
cmd = StatsCommands(config)
|
|
asyncio.run(cmd.poc_stats(year))
|
|
|
|
@stats.command('rules')
|
|
@click.option('--year', type=int, help='Rule statistics for specific year')
|
|
@click.option('--method', type=click.Choice(['template', 'llm', 'hybrid']), help='Filter by generation method')
|
|
@pass_config
|
|
def stats_rules(config, year, method):
|
|
"""Generate rule generation statistics"""
|
|
cmd = StatsCommands(config)
|
|
asyncio.run(cmd.rule_stats(year, method))
|
|
|
|
# Export commands
|
|
@cli.group()
|
|
@pass_config
|
|
def export(config):
|
|
"""Export rules in various formats"""
|
|
pass
|
|
|
|
@export.command('sigma')
|
|
@click.argument('output_dir', type=click.Path())
|
|
@click.option('--year', type=int, help='Export rules for specific year')
|
|
@click.option('--format', type=click.Choice(['yaml', 'json']), default='yaml', help='Output format')
|
|
@click.option('--method', type=click.Choice(['template', 'llm', 'hybrid']), help='Filter by generation method')
|
|
@pass_config
|
|
def export_sigma(config, output_dir, year, format, method):
|
|
"""Export SIGMA rules to a directory"""
|
|
cmd = ExportCommands(config)
|
|
asyncio.run(cmd.export_sigma_rules(output_dir, year, format, method))
|
|
|
|
@export.command('metadata')
|
|
@click.argument('output_file', type=click.Path())
|
|
@click.option('--year', type=int, help='Export metadata for specific year')
|
|
@click.option('--format', type=click.Choice(['json', 'csv']), default='json', help='Output format')
|
|
@pass_config
|
|
def export_metadata(config, output_file, year, format):
|
|
"""Export CVE metadata"""
|
|
cmd = ExportCommands(config)
|
|
asyncio.run(cmd.export_metadata(output_file, year, format))
|
|
|
|
# Migration commands (for transitioning from web app)
|
|
@cli.group()
|
|
@pass_config
|
|
def migrate(config):
|
|
"""Migration utilities for transitioning from web application"""
|
|
pass
|
|
|
|
@migrate.command('from-database')
|
|
@click.option('--database-url', help='Database URL to migrate from')
|
|
@click.option('--batch-size', '-b', default=100, help='Batch size for migration')
|
|
@click.option('--dry-run', is_flag=True, help='Show what would be migrated without doing it')
|
|
@pass_config
|
|
def migrate_from_database(config, database_url, batch_size, dry_run):
|
|
"""Migrate data from existing database to file structure"""
|
|
cmd = MigrateCommands(config)
|
|
asyncio.run(cmd.migrate_from_database(database_url, batch_size, dry_run))
|
|
|
|
@migrate.command('validate')
|
|
@click.option('--year', type=int, help='Validate specific year')
|
|
@pass_config
|
|
def migrate_validate(config, year):
|
|
"""Validate migrated data integrity"""
|
|
cmd = MigrateCommands(config)
|
|
asyncio.run(cmd.validate_migration(year))
|
|
|
|
# Utility commands
|
|
@cli.command()
|
|
@pass_config
|
|
def version(config):
|
|
"""Show version information"""
|
|
click.echo("SIGMA CLI v1.0.0")
|
|
click.echo("CVE-SIGMA Auto Generator - File-based Edition")
|
|
|
|
@cli.command()
|
|
@pass_config
|
|
def config_init(config):
|
|
"""Initialize CLI configuration"""
|
|
config_data = {
|
|
'base_dir': str(config.base_dir),
|
|
'api_keys': {
|
|
'nvd_api_key': '',
|
|
'github_token': '',
|
|
'openai_api_key': '',
|
|
'anthropic_api_key': ''
|
|
},
|
|
'llm_settings': {
|
|
'default_provider': 'ollama',
|
|
'default_model': 'llama3.2',
|
|
'ollama_base_url': 'http://localhost:11434'
|
|
},
|
|
'processing': {
|
|
'default_batch_size': 50,
|
|
'default_methods': ['template']
|
|
}
|
|
}
|
|
|
|
config.config_file.parent.mkdir(exist_ok=True)
|
|
with open(config.config_file, 'w') as f:
|
|
import yaml
|
|
yaml.dump(config_data, f, default_flow_style=False)
|
|
|
|
click.echo(f"Configuration initialized at {config.config_file}")
|
|
click.echo("Please edit the configuration file to add your API keys and preferences.")
|
|
|
|
if __name__ == '__main__':
|
|
cli() |