Implement complete database schema and Flask application structure for SneakyScan web interface. This establishes the foundation for web-based scan management, scheduling, and visualization. Database & ORM: - Add 11 SQLAlchemy models for comprehensive scan data storage (Scan, ScanSite, ScanIP, ScanPort, ScanService, ScanCertificate, ScanTLSVersion, Schedule, Alert, AlertRule, Setting) - Configure Alembic migrations system with initial schema migration - Add init_db.py script for database initialization and password setup - Support both migration-based and direct table creation Settings System: - Implement SettingsManager with automatic encryption for sensitive values - Add Fernet encryption for SMTP passwords and API tokens - Implement PasswordManager with bcrypt password hashing (work factor 12) - Initialize default settings for SMTP, authentication, and retention Flask Application: - Create Flask app factory pattern with scoped session management - Add 4 API blueprints: scans, schedules, alerts, settings - Implement functional Settings API (GET/PUT/DELETE endpoints) - Add CORS support, error handlers, and request/response logging - Configure development and production logging to file and console Docker & Deployment: - Update Dockerfile to install Flask dependencies - Add docker-compose-web.yml for web application deployment - Configure volume mounts for database, output, and logs persistence - Expose port 5000 for Flask web server Testing & Validation: - Add validate_phase1.py script to verify all deliverables - Validate directory structure, Python syntax, models, and endpoints - All validation checks passing Documentation: - Add PHASE1_COMPLETE.md with comprehensive Phase 1 summary - Update ROADMAP.md with Phase 1 completion status - Update .gitignore to exclude database files and documentation Files changed: 21 files - New: web/ directory with complete Flask app structure - New: migrations/ with Alembic configuration - New: requirements-web.txt with Flask dependencies - Modified: Dockerfile, ROADMAP.md, .gitignore
198 lines
6.2 KiB
Python
Executable File
198 lines
6.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Phase 1 validation script.
|
|
|
|
Validates that all Phase 1 deliverables are in place and code structure is correct.
|
|
Does not require dependencies to be installed.
|
|
"""
|
|
|
|
import ast
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def validate_file_exists(file_path, description):
|
|
"""Check if a file exists."""
|
|
if Path(file_path).exists():
|
|
print(f"✓ {description}: {file_path}")
|
|
return True
|
|
else:
|
|
print(f"✗ {description} missing: {file_path}")
|
|
return False
|
|
|
|
|
|
def validate_directory_exists(dir_path, description):
|
|
"""Check if a directory exists."""
|
|
if Path(dir_path).is_dir():
|
|
print(f"✓ {description}: {dir_path}")
|
|
return True
|
|
else:
|
|
print(f"✗ {description} missing: {dir_path}")
|
|
return False
|
|
|
|
|
|
def validate_python_syntax(file_path):
|
|
"""Validate Python file syntax."""
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
ast.parse(f.read())
|
|
return True
|
|
except SyntaxError as e:
|
|
print(f" ✗ Syntax error in {file_path}: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Run all validation checks."""
|
|
print("=" * 70)
|
|
print("SneakyScanner Phase 1 Validation")
|
|
print("=" * 70)
|
|
|
|
all_passed = True
|
|
|
|
# Check project structure
|
|
print("\n1. Project Structure:")
|
|
print("-" * 70)
|
|
|
|
structure_checks = [
|
|
("web/", "Web application directory"),
|
|
("web/api/", "API blueprints directory"),
|
|
("web/templates/", "Jinja2 templates directory"),
|
|
("web/static/", "Static files directory"),
|
|
("web/utils/", "Utility modules directory"),
|
|
("migrations/", "Alembic migrations directory"),
|
|
("migrations/versions/", "Migration versions directory"),
|
|
]
|
|
|
|
for path, desc in structure_checks:
|
|
if not validate_directory_exists(path, desc):
|
|
all_passed = False
|
|
|
|
# Check core files
|
|
print("\n2. Core Files:")
|
|
print("-" * 70)
|
|
|
|
core_files = [
|
|
("requirements-web.txt", "Web dependencies"),
|
|
("alembic.ini", "Alembic configuration"),
|
|
("init_db.py", "Database initialization script"),
|
|
("docker-compose-web.yml", "Docker Compose for web app"),
|
|
]
|
|
|
|
for path, desc in core_files:
|
|
if not validate_file_exists(path, desc):
|
|
all_passed = False
|
|
|
|
# Check Python modules
|
|
print("\n3. Python Modules:")
|
|
print("-" * 70)
|
|
|
|
python_modules = [
|
|
("web/__init__.py", "Web package init"),
|
|
("web/models.py", "SQLAlchemy models"),
|
|
("web/app.py", "Flask application factory"),
|
|
("web/utils/__init__.py", "Utils package init"),
|
|
("web/utils/settings.py", "Settings manager"),
|
|
("web/api/__init__.py", "API package init"),
|
|
("web/api/scans.py", "Scans API blueprint"),
|
|
("web/api/schedules.py", "Schedules API blueprint"),
|
|
("web/api/alerts.py", "Alerts API blueprint"),
|
|
("web/api/settings.py", "Settings API blueprint"),
|
|
("migrations/env.py", "Alembic environment"),
|
|
("migrations/script.py.mako", "Migration template"),
|
|
("migrations/versions/001_initial_schema.py", "Initial migration"),
|
|
]
|
|
|
|
for path, desc in python_modules:
|
|
exists = validate_file_exists(path, desc)
|
|
if exists:
|
|
# Skip syntax check for .mako templates (they're not pure Python)
|
|
if not path.endswith('.mako'):
|
|
if not validate_python_syntax(path):
|
|
all_passed = False
|
|
else:
|
|
print(f" (Skipped syntax check for template file)")
|
|
else:
|
|
all_passed = False
|
|
|
|
# Check models
|
|
print("\n4. Database Models (from models.py):")
|
|
print("-" * 70)
|
|
|
|
try:
|
|
# Read models.py and look for class definitions
|
|
with open('web/models.py', 'r') as f:
|
|
content = f.read()
|
|
tree = ast.parse(content)
|
|
|
|
models = []
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.ClassDef) and node.name != 'Base':
|
|
models.append(node.name)
|
|
|
|
expected_models = [
|
|
'Scan', 'ScanSite', 'ScanIP', 'ScanPort', 'ScanService',
|
|
'ScanCertificate', 'ScanTLSVersion', 'Schedule', 'Alert',
|
|
'AlertRule', 'Setting'
|
|
]
|
|
|
|
for model in expected_models:
|
|
if model in models:
|
|
print(f"✓ Model defined: {model}")
|
|
else:
|
|
print(f"✗ Model missing: {model}")
|
|
all_passed = False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Failed to parse models.py: {e}")
|
|
all_passed = False
|
|
|
|
# Check API endpoints
|
|
print("\n5. API Blueprints:")
|
|
print("-" * 70)
|
|
|
|
blueprints = {
|
|
'web/api/scans.py': ['list_scans', 'get_scan', 'trigger_scan', 'delete_scan'],
|
|
'web/api/schedules.py': ['list_schedules', 'get_schedule', 'create_schedule'],
|
|
'web/api/alerts.py': ['list_alerts', 'list_alert_rules'],
|
|
'web/api/settings.py': ['get_settings', 'update_settings'],
|
|
}
|
|
|
|
for blueprint_file, expected_funcs in blueprints.items():
|
|
try:
|
|
with open(blueprint_file, 'r') as f:
|
|
content = f.read()
|
|
tree = ast.parse(content)
|
|
|
|
functions = [node.name for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
|
|
|
|
print(f"\n {blueprint_file}:")
|
|
for func in expected_funcs:
|
|
if func in functions:
|
|
print(f" ✓ Endpoint: {func}")
|
|
else:
|
|
print(f" ✗ Missing endpoint: {func}")
|
|
all_passed = False
|
|
except Exception as e:
|
|
print(f" ✗ Failed to parse {blueprint_file}: {e}")
|
|
all_passed = False
|
|
|
|
# Summary
|
|
print("\n" + "=" * 70)
|
|
if all_passed:
|
|
print("✓ All Phase 1 validation checks passed!")
|
|
print("\nNext steps:")
|
|
print("1. Install dependencies: pip install -r requirements-web.txt")
|
|
print("2. Initialize database: python3 init_db.py --password YOUR_PASSWORD")
|
|
print("3. Run Flask app: python3 -m web.app")
|
|
print("4. Test API: curl http://localhost:5000/api/settings/health")
|
|
return 0
|
|
else:
|
|
print("✗ Some validation checks failed. Please review errors above.")
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|