Skip to content

Development Guide

Ryan edited this page Jan 8, 2026 · 1 revision

Development Guide

This guide provides comprehensive information for developers who want to contribute to, extend, or customize the Linux Security Audit Project.

Table of Contents

Getting Started

Prerequisites

Required Knowledge:

  • Python 3.6+ programming
  • Linux system administration
  • Security concepts and standards
  • Command-line proficiency
  • Git version control

Recommended Knowledge:

  • Security frameworks (CIS, NIST, etc.)
  • Linux security mechanisms (SELinux, AppArmor)
  • Shell scripting
  • Regular expressions
  • JSON/XML data formats

Development Tools

Essential Tools:

# Python 3.6+
python3 --version

# Git
git --version

# Text editor/IDE (choose one)
vim, nano, VS Code, PyCharm, etc.

# Optional: Virtual environment
python3 -m venv venv
source venv/bin/activate  # Linux/Mac

Recommended Tools:

# Code formatting
pip install black

# Linting
pip install pylint flake8

# Type checking
pip install mypy

# Testing
pip install pytest

Project Setup

  1. Fork and Clone Repository:
# Fork on GitHub first, then:
git clone https://github.com/YOUR_USERNAME/Linux-Security-Audit-Project.git
cd Linux-Security-Audit-Project

# Add upstream remote
git remote add upstream https://github.com/Sandler73/Linux-Security-Audit-Project.git
  1. Create Development Branch:
git checkout -b feature/your-feature-name
# or
git checkout -b bugfix/issue-description
  1. Verify Setup:
# List available modules
python3 linux_security_audit.py --list-modules

# Run basic test
python3 linux_security_audit.py -m Core -f Console

Development Environment

Directory Structure

Linux-Security-Audit-Project/
├── linux_security_audit.py      # Main orchestrator
├── module_core.py                # Core security module
├── module_cis.py                 # CIS Benchmarks module
├── module_cisa.py                # CISA guidance module
├── module_enisa.py               # ENISA guidelines module
├── module_iso27001.py            # ISO 27001 controls module
├── module_nist.py                # NIST frameworks module
├── module_nsa.py                 # NSA security module
├── module_stig.py                # DISA STIG module
├── README.md                     # Main documentation
├── LICENSE                       # MIT License
├── CHANGELOG.md                  # Version history
├── SECURITY.md                   # Security policy
├── .gitignore                    # Git ignore rules
└── .github/                      # GitHub-specific files
    ├── workflows/                # CI/CD workflows
    └── ISSUE_TEMPLATE/           # Issue templates

Environment Variables

Optional Configuration:

# Set custom module path (if needed)
export SECURITY_AUDIT_MODULE_PATH="/path/to/custom/modules"

# Set output directory default
export SECURITY_AUDIT_OUTPUT_DIR="/var/log/security-audits"

# Enable debug mode
export SECURITY_AUDIT_DEBUG=1

Virtual Environment Setup

Recommended for Development:

# Create virtual environment
python3 -m venv audit-dev-env

# Activate
source audit-dev-env/bin/activate  # Linux/Mac
audit-dev-env\Scripts\activate     # Windows

# Deactivate when done
deactivate

Project Architecture

Component Overview

┌─────────────────────────────────────────────────────────────┐
│                    linux_security_audit.py                  │
│                    (Main Orchestrator)                      │
├─────────────────────────────────────────────────────────────┤
│  - Argument parsing                                         │
│  - Module discovery and validation                          │
│  - Privilege checking                                       │
│  - Module execution coordination                            │
│  - Result aggregation and validation                        │
│  - Output generation (HTML, CSV, JSON, XML, Console)       │
│  - Remediation orchestration                                │
└─────────────────────────────────────────────────────────────┘
                            │
                            ├───────────────┬──────────────┬─────────────┐
                            │               │              │             │
                            ▼               ▼              ▼             ▼
                    ┌───────────────┐ ┌──────────┐ ┌──────────┐  ┌──────────┐
                    │ module_core.py│ │module_cis│ │module_nist│  │module_...│
                    │               │ │          │ │          │  │          │
                    │ run_module()  │ │run_module│ │run_module│  │run_module│
                    │     ↓         │ │    ↓     │ │    ↓     │  │    ↓     │
                    │ check_*()     │ │check_*() │ │check_*() │  │check_*() │
                    │     ↓         │ │    ↓     │ │    ↓     │  │    ↓     │
                    │ AuditResult[] │ │AuditResult││AuditResult│  │AuditResult│
                    └───────────────┘ └──────────┘ └──────────┘  └──────────┘

Core Data Structures

AuditResult Class

Purpose: Standardized container for check results

@dataclass
class AuditResult:
    module: str          # Module name (Core, CIS, NIST, etc.)
    category: str        # Check category/area
    status: str          # Pass, Fail, Warning, Info, Error
    message: str         # Brief description
    details: str = ""    # Detailed explanation
    remediation: str = ""  # Fix command(s)
    timestamp: str = field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        return asdict(self)
    
    def validate(self) -> Tuple[bool, List[str]]:
        """Validate the result object"""
        # Validation logic

Valid Status Values:

  • Pass: Check passed successfully
  • Fail: Security issue detected
  • Warning: Potential security concern
  • Info: Informational finding
  • Error: Check could not be completed

ExecutionInfo Class

Purpose: Metadata about audit execution

@dataclass
class ExecutionInfo:
    hostname: str
    os_version: str
    scan_date: str
    duration: str
    modules_run: List[str]
    total_checks: int
    pass_count: int
    fail_count: int
    warning_count: int
    info_count: int
    error_count: int

Shared Data Dictionary

Purpose: Pass context between orchestrator and modules

shared_data = {
    "hostname": str,        # System hostname
    "os_version": str,      # OS version string
    "scan_date": datetime,  # Scan start time
    "is_root": bool,        # Running as root?
    "script_path": Path     # Script directory path
}

Module Interface Contract

Every module MUST implement:

def run_module(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """
    Main module entry point
    
    Args:
        shared_data: Dictionary with hostname, os_version, is_root, etc.
        
    Returns:
        List of AuditResult objects
        
    Raises:
        Exception: For fatal errors only
    """
    pass

Module Discovery Process

  1. Scan Directory: Orchestrator scans for module_*.py files
  2. Dynamic Import: Imports module using importlib
  3. Validate Interface: Checks for run_module() function
  4. Register Module: Adds to available modules dictionary
  5. Execute on Demand: Calls run_module() when requested
def get_available_modules() -> Dict[str, Any]:
    """Discover and validate security modules"""
    modules = {}
    
    # Find all module_*.py files
    for module_file in SCRIPT_PATH.glob("module_*.py"):
        module_name = module_file.stem.replace("module_", "").upper()
        
        try:
            # Import module
            spec = importlib.util.spec_from_file_location(module_file.stem, module_file)
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            
            # Verify interface
            if hasattr(module, 'run_module'):
                modules[module_name] = module
                
        except Exception as e:
            print(f"[!] Failed to load module {module_name}: {e}")
    
    return modules

Creating Security Modules

Module Development Workflow

  1. Plan Module Scope

    • Define security framework/standard
    • Identify check categories
    • List specific security controls
    • Determine OS-specific requirements
  2. Create Module File

    • Name: module_<name>.py
    • Add module header with documentation
    • Implement required interface
  3. Develop Check Functions

    • Create category-specific check functions
    • Return AuditResult objects
    • Handle errors gracefully
  4. Test Module

    • Standalone testing
    • Integration testing
    • Root and non-root testing
  5. Document Module

    • Update module documentation
    • Add to README
    • Document check details

Module Template

File: module_custom.py

#!/usr/bin/env python3
"""
module_custom.py
Custom Security Module
Version: 1.0

SYNOPSIS:
    Brief description of module purpose

DESCRIPTION:
    Detailed description of what this module checks:
    
    Categories Covered:
    - Category 1: Description
    - Category 2: Description
    - Category 3: Description
    
    Standards Referenced:
    - Standard 1
    - Standard 2

PARAMETERS:
    shared_data : Dictionary containing shared data from main script

USAGE:
    Standalone testing:
        python3 module_custom.py
    
    Integration with main audit script:
        python3 linux_security_audit.py --modules CUSTOM
        python3 linux_security_audit.py -m CUSTOM

NOTES:
    Version: 1.0
    Author: Your Name
    Standards: Relevant standards
    Target: Expected number of checks
"""

import os
import sys
import re
import subprocess
import pwd
import grp
from pathlib import Path
from typing import List, Dict, Any, Optional, Tuple

# Import AuditResult from main script
sys.path.insert(0, str(Path(__file__).parent))
from linux_security_audit import AuditResult

# Module constants
MODULE_NAME = "CUSTOM"
MODULE_VERSION = "1.0"

# ============================================================================
# Utility Functions
# ============================================================================

def run_command(cmd: str, check: bool = True) -> Tuple[int, str, str]:
    """
    Execute shell command and return results
    
    Args:
        cmd: Command to execute
        check: Whether to raise exception on error
        
    Returns:
        Tuple of (return_code, stdout, stderr)
    """
    try:
        result = subprocess.run(
            cmd,
            shell=True,
            capture_output=True,
            text=True,
            timeout=30
        )
        return result.returncode, result.stdout, result.stderr
    except subprocess.TimeoutExpired:
        return -1, "", "Command timed out"
    except Exception as e:
        return -1, "", str(e)

def check_file_exists(path: str) -> bool:
    """Check if file exists"""
    return os.path.isfile(path)

def check_file_permissions(path: str, expected_mode: int) -> bool:
    """
    Check file permissions
    
    Args:
        path: File path
        expected_mode: Expected octal mode (e.g., 0o600)
        
    Returns:
        True if permissions match
    """
    try:
        actual_mode = os.stat(path).st_mode & 0o777
        return actual_mode == expected_mode
    except:
        return False

def read_file_safe(path: str) -> Optional[str]:
    """Safely read file contents"""
    try:
        with open(path, 'r') as f:
            return f.read()
    except:
        return None

# ============================================================================
# Check Functions
# ============================================================================

def check_example_category(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """
    Example security checks for a category
    
    Args:
        shared_data: Shared data from main script
        
    Returns:
        List of AuditResult objects
    """
    results = []
    is_root = shared_data.get("is_root", False)
    
    # Example Check 1: File exists
    try:
        if check_file_exists("/etc/security/example.conf"):
            result = AuditResult(
                module=MODULE_NAME,
                category="Configuration",
                status="Pass",
                message="Security configuration file exists",
                details="Found /etc/security/example.conf",
                remediation=""
            )
        else:
            result = AuditResult(
                module=MODULE_NAME,
                category="Configuration",
                status="Fail",
                message="Security configuration file missing",
                details="/etc/security/example.conf not found",
                remediation="sudo touch /etc/security/example.conf && sudo chmod 600 /etc/security/example.conf"
            )
        results.append(result)
        
    except Exception as e:
        result = AuditResult(
            module=MODULE_NAME,
            category="Configuration",
            status="Error",
            message="Could not check configuration file",
            details=f"Error: {str(e)}"
        )
        results.append(result)
    
    # Example Check 2: Command output
    try:
        returncode, stdout, stderr = run_command("systemctl is-active example-service")
        
        if returncode == 0 and stdout.strip() == "active":
            result = AuditResult(
                module=MODULE_NAME,
                category="Services",
                status="Pass",
                message="Example service is active",
                details="systemctl reports service is running"
            )
        else:
            result = AuditResult(
                module=MODULE_NAME,
                category="Services",
                status="Fail",
                message="Example service is not active",
                details=f"Service state: {stdout.strip()}",
                remediation="sudo systemctl enable example-service && sudo systemctl start example-service"
            )
        results.append(result)
        
    except Exception as e:
        result = AuditResult(
            module=MODULE_NAME,
            category="Services",
            status="Error",
            message="Could not check service status",
            details=f"Error: {str(e)}"
        )
        results.append(result)
    
    # Example Check 3: Privilege-aware check
    if is_root:
        try:
            # Check that requires root privileges
            returncode, stdout, stderr = run_command("cat /etc/shadow | wc -l")
            
            if returncode == 0:
                user_count = int(stdout.strip())
                result = AuditResult(
                    module=MODULE_NAME,
                    category="User Accounts",
                    status="Info",
                    message=f"System has {user_count} user accounts",
                    details="Count from /etc/shadow"
                )
            else:
                result = AuditResult(
                    module=MODULE_NAME,
                    category="User Accounts",
                    status="Error",
                    message="Could not read shadow file",
                    details=stderr
                )
            results.append(result)
            
        except Exception as e:
            result = AuditResult(
                module=MODULE_NAME,
                category="User Accounts",
                status="Error",
                message="Failed to check user accounts",
                details=str(e)
            )
            results.append(result)
    else:
        # Non-root alternative or skip
        result = AuditResult(
            module=MODULE_NAME,
            category="User Accounts",
            status="Error",
            message="Cannot check user accounts",
            details="Requires root privileges"
        )
        results.append(result)
    
    return results

def check_another_category(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """
    Another category of security checks
    
    Args:
        shared_data: Shared data from main script
        
    Returns:
        List of AuditResult objects
    """
    results = []
    
    # Add your checks here
    # Follow the same pattern as check_example_category
    
    return results

# ============================================================================
# Main Module Entry Point
# ============================================================================

def run_module(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """
    Main module execution function - REQUIRED
    
    This is the entry point called by the main orchestrator.
    
    Args:
        shared_data: Dictionary containing:
            - hostname: str
            - os_version: str
            - scan_date: datetime
            - is_root: bool
            - script_path: Path
            
    Returns:
        List of AuditResult objects from all checks
        
    Raises:
        Exception: Only for fatal errors that prevent module execution
    """
    results = []
    
    # Optional: Check prerequisites
    is_root = shared_data.get("is_root", False)
    if not is_root:
        print(f"[!] {MODULE_NAME}: Running without root - some checks will be limited")
    
    # Execute check functions
    try:
        results.extend(check_example_category(shared_data))
        results.extend(check_another_category(shared_data))
        # Add more check functions as needed
        
    except Exception as e:
        # Only catch truly fatal errors here
        print(f"[!] {MODULE_NAME}: Fatal error - {str(e)}")
        raise
    
    return results

# ============================================================================
# Standalone Testing
# ============================================================================

if __name__ == "__main__":
    """
    Standalone module testing
    
    Run with: python3 module_custom.py
    """
    import socket
    from datetime import datetime
    
    print(f"\n{'=' * 80}")
    print(f"{MODULE_NAME} Module - Standalone Test")
    print(f"Version: {MODULE_VERSION}")
    print(f"{'=' * 80}\n")
    
    # Create test shared_data
    test_data = {
        "hostname": socket.gethostname(),
        "os_version": "Linux Test",
        "scan_date": datetime.now(),
        "is_root": os.geteuid() == 0,
        "script_path": Path(__file__).parent
    }
    
    print(f"Test Environment:")
    print(f"  Hostname: {test_data['hostname']}")
    print(f"  Root: {test_data['is_root']}")
    print(f"\n{'=' * 80}\n")
    
    # Run module
    try:
        results = run_module(test_data)
        
        # Display results
        print(f"Results Summary:")
        print(f"  Total Checks: {len(results)}")
        
        # Count by status
        status_counts = {}
        for result in results:
            status_counts[result.status] = status_counts.get(result.status, 0) + 1
        
        for status, count in sorted(status_counts.items()):
            print(f"  {status}: {count}")
        
        print(f"\n{'=' * 80}\n")
        print(f"Detailed Results:\n")
        
        # Display each result
        for i, result in enumerate(results, 1):
            print(f"{i}. [{result.status}] {result.category}")
            print(f"   Message: {result.message}")
            if result.details:
                print(f"   Details: {result.details}")
            if result.remediation:
                print(f"   Remediation: {result.remediation}")
            print()
        
        print(f"{'=' * 80}")
        print(f"Test completed successfully!")
        
    except Exception as e:
        print(f"[!] Error running module: {str(e)}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

Check Function Best Practices

1. Consistent Structure

def check_something(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """Clear docstring describing what is checked"""
    results = []
    is_root = shared_data.get("is_root", False)
    
    try:
        # Perform check
        # Create result
        # Append to results
        
    except PermissionError:
        # Handle specific exception
        result = AuditResult(
            module=MODULE_NAME,
            category="Category",
            status="Error",
            message="Permission denied",
            details="Requires root privileges"
        )
        results.append(result)
        
    except Exception as e:
        # Handle general exception
        result = AuditResult(
            module=MODULE_NAME,
            category="Category",
            status="Error",
            message="Check failed",
            details=f"Error: {str(e)}"
        )
        results.append(result)
    
    return results

2. Privilege-Aware Checks

def check_privileged_item(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """Check that requires elevated privileges"""
    results = []
    is_root = shared_data.get("is_root", False)
    
    if not is_root:
        # Graceful degradation
        result = AuditResult(
            module=MODULE_NAME,
            category="Privileged Check",
            status="Error",
            message="Cannot perform check without root",
            details="Run with sudo for complete results"
        )
        results.append(result)
        return results
    
    # Perform privileged check
    try:
        # Root-only operations
        pass
    except Exception as e:
        # Handle errors
        pass
    
    return results

3. OS-Specific Checks

def check_os_specific(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """OS-aware security check"""
    results = []
    
    # Detect OS
    if os.path.isfile("/etc/debian_version"):
        # Debian-based logic
        cmd = "apt list --upgradable"
        pkg_manager = "apt"
    elif os.path.isfile("/etc/redhat-release"):
        # RedHat-based logic
        cmd = "yum list updates"
        pkg_manager = "yum"
    else:
        # Unknown OS
        result = AuditResult(
            module=MODULE_NAME,
            category="OS Detection",
            status="Error",
            message="Unsupported OS",
            details="This check requires Debian or RedHat family"
        )
        results.append(result)
        return results
    
    # Perform OS-specific check
    # ...
    
    return results

4. File Permission Checks

def check_file_permissions_example(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """Check file has correct permissions"""
    results = []
    
    file_path = "/etc/ssh/sshd_config"
    expected_mode = 0o600  # rw-------
    
    try:
        if not os.path.exists(file_path):
            result = AuditResult(
                module=MODULE_NAME,
                category="File Permissions",
                status="Error",
                message=f"File not found: {file_path}",
                details="SSH configuration file does not exist"
            )
            results.append(result)
            return results
        
        # Get actual permissions
        stat_info = os.stat(file_path)
        actual_mode = stat_info.st_mode & 0o777
        
        if actual_mode == expected_mode:
            result = AuditResult(
                module=MODULE_NAME,
                category="File Permissions",
                status="Pass",
                message=f"{file_path} has correct permissions",
                details=f"Permissions: {oct(actual_mode)}"
            )
        else:
            result = AuditResult(
                module=MODULE_NAME,
                category="File Permissions",
                status="Fail",
                message=f"{file_path} has incorrect permissions",
                details=f"Expected: {oct(expected_mode)}, Found: {oct(actual_mode)}",
                remediation=f"sudo chmod 600 {file_path}"
            )
        
        results.append(result)
        
    except PermissionError:
        result = AuditResult(
            module=MODULE_NAME,
            category="File Permissions",
            status="Error",
            message="Permission denied checking file",
            details="Requires root privileges"
        )
        results.append(result)
    
    return results

5. Configuration File Parsing

def check_config_setting(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """Check configuration file setting"""
    results = []
    
    config_file = "/etc/ssh/sshd_config"
    setting = "PermitRootLogin"
    expected_value = "no"
    
    try:
        with open(config_file, 'r') as f:
            content = f.read()
        
        # Parse configuration
        found = False
        actual_value = None
        
        for line in content.split('\n'):
            line = line.strip()
            
            # Skip comments and empty lines
            if not line or line.startswith('#'):
                continue
            
            # Check for setting
            if line.startswith(setting):
                found = True
                parts = line.split()
                if len(parts) >= 2:
                    actual_value = parts[1].lower()
                break
        
        if not found:
            result = AuditResult(
                module=MODULE_NAME,
                category="SSH Configuration",
                status="Warning",
                message=f"{setting} not explicitly set",
                details="Using default value",
                remediation=f"echo '{setting} {expected_value}' | sudo tee -a {config_file}"
            )
        elif actual_value == expected_value.lower():
            result = AuditResult(
                module=MODULE_NAME,
                category="SSH Configuration",
                status="Pass",
                message=f"{setting} is correctly configured",
                details=f"Value: {actual_value}"
            )
        else:
            result = AuditResult(
                module=MODULE_NAME,
                category="SSH Configuration",
                status="Fail",
                message=f"{setting} is incorrectly configured",
                details=f"Expected: {expected_value}, Found: {actual_value}",
                remediation=f"sudo sed -i 's/^{setting}.*/{setting} {expected_value}/' {config_file} && sudo systemctl restart sshd"
            )
        
        results.append(result)
        
    except FileNotFoundError:
        result = AuditResult(
            module=MODULE_NAME,
            category="SSH Configuration",
            status="Error",
            message=f"Configuration file not found: {config_file}",
            details="SSH may not be installed"
        )
        results.append(result)
    
    except PermissionError:
        result = AuditResult(
            module=MODULE_NAME,
            category="SSH Configuration",
            status="Error",
            message="Permission denied reading configuration",
            details="Requires root privileges"
        )
        results.append(result)
    
    return results

Code Standards

Python Style Guide

Follow PEP 8: Python Enhancement Proposal 8

Key Standards:

  • Indentation: 4 spaces (no tabs)
  • Line length: 100 characters max
  • Blank lines: 2 between top-level definitions, 1 between methods
  • Imports: Standard library, third-party, local (separated by blank line)
  • Naming:
    • Functions/variables: lowercase_with_underscores
    • Classes: CapitalizedWords
    • Constants: UPPERCASE_WITH_UNDERSCORES
    • Modules: lowercase_no_underscores

Example:

import os  # Standard library
import sys

from typing import List, Dict  # Third-party

from linux_security_audit import AuditResult  # Local

# Constants
MODULE_NAME = "EXAMPLE"
MAX_RETRIES = 3

# Class
class SecurityChecker:
    """Security check implementation"""
    
    def __init__(self, config: Dict):
        self.config = config
    
    def check_setting(self, name: str) -> bool:
        """Check a specific setting"""
        return True

# Function
def run_security_check(param: str) -> List[AuditResult]:
    """Execute security check"""
    results = []
    return results

Documentation Standards

Module Header

"""
module_name.py
Module Title
Version: X.X

SYNOPSIS:
    One-line description

DESCRIPTION:
    Multi-line detailed description
    
    What this module checks:
    - Category 1: Description
    - Category 2: Description
    
    Standards covered:
    - Standard 1
    - Standard 2

PARAMETERS:
    shared_data : Dictionary containing shared data from main script

USAGE:
    Standalone testing:
        python3 module_name.py
    
    Integration:
        python3 linux_security_audit.py --modules NAME

NOTES:
    Version: X.X
    Author: Name (optional)
    Reference: URL to standards
    Target: Number of checks
"""

Function Docstrings

def function_name(param1: type, param2: type) -> return_type:
    """
    Brief description of function purpose
    
    More detailed description if needed. Explain what the
    function does and any important behavior.
    
    Args:
        param1: Description of first parameter
        param2: Description of second parameter
        
    Returns:
        Description of return value
        
    Raises:
        ExceptionType: When this exception is raised
        
    Example:
        >>> function_name("test", 42)
        expected_result
    """
    pass

Inline Comments

# Good comments explain WHY, not WHAT
def check_password_policy(shared_data: Dict[str, Any]) -> List[AuditResult]:
    results = []
    
    # CIS Benchmark 5.4.1 requires password expiration <= 365 days
    max_days = 365
    
    try:
        # /etc/login.defs contains system-wide password policy
        with open("/etc/login.defs", 'r') as f:
            content = f.read()
        
        # Extract PASS_MAX_DAYS setting
        for line in content.split('\n'):
            if line.startswith("PASS_MAX_DAYS"):
                # Parse value (format: "PASS_MAX_DAYS    90")
                parts = line.split()
                if len(parts) >= 2:
                    current_days = int(parts[1])
                    break
        
        # Compare against CIS requirement
        if current_days <= max_days:
            # Compliant with CIS Benchmark
            status = "Pass"
        else:
            # Non-compliant
            status = "Fail"
            
    except Exception as e:
        # File read or parsing failed
        pass
    
    return results

Error Handling Standards

Specific Exception Handling:

try:
    # Operation
    pass
except FileNotFoundError:
    # Handle missing file specifically
    pass
except PermissionError:
    # Handle permission issue specifically
    pass
except ValueError:
    # Handle value parsing specifically
    pass
except Exception as e:
    # Generic catch-all as last resort
    pass

Never Use Bare Except:

# BAD
try:
    something()
except:  # Don't do this
    pass

# GOOD
try:
    something()
except Exception as e:
    # Handle with error details
    print(f"Error: {str(e)}")

Testing Guidelines

Unit Testing

Test Structure:

# test_module_custom.py
import unittest
from unittest.mock import patch, MagicMock
from module_custom import check_example_category, run_module

class TestCustomModule(unittest.TestCase):
    
    def setUp(self):
        """Set up test fixtures"""
        self.test_data = {
            "hostname": "test-host",
            "is_root": True,
            "os_version": "Linux Test",
            "scan_date": "2025-01-07",
            "script_path": Path(".")
        }
    
    def test_check_example_category(self):
        """Test example category check function"""
        results = check_example_category(self.test_data)
        
        # Verify results list
        self.assertIsInstance(results, list)
        self.assertGreater(len(results), 0)
        
        # Verify result objects
        for result in results:
            self.assertIn(result.status, ["Pass", "Fail", "Warning", "Info", "Error"])
            self.assertIsNotNone(result.message)
    
    @patch('module_custom.run_command')
    def test_service_check_active(self, mock_run_command):
        """Test service check when service is active"""
        mock_run_command.return_value = (0, "active\n", "")
        
        results = check_example_category(self.test_data)
        
        # Find service check result
        service_result = [r for r in results if r.category == "Services"][0]
        self.assertEqual(service_result.status, "Pass")
    
    def test_non_root_execution(self):
        """Test module behavior without root"""
        test_data = self.test_data.copy()
        test_data["is_root"] = False
        
        results = run_module(test_data)
        
        # Should still return results
        self.assertIsInstance(results, list)
        
        # Some checks may be limited
        error_results = [r for r in results if r.status == "Error"]
        self.assertGreaterEqual(len(error_results), 0)

if __name__ == '__main__':
    unittest.main()

Integration Testing

Test Full Module Integration:

#!/bin/bash
# test_integration.sh

echo "=== Integration Test Suite ==="

# Test 1: Module discovery
echo "Test 1: Module Discovery"
python3 linux_security_audit.py --list-modules | grep -q "CUSTOM"
if [ $? -eq 0 ]; then
    echo "  PASS: Module discovered"
else
    echo "  FAIL: Module not discovered"
    exit 1
fi

# Test 2: Module execution (non-root)
echo "Test 2: Non-root Execution"
python3 linux_security_audit.py -m CUSTOM -f Console > /tmp/test_output.txt 2>&1
if [ $? -eq 0 ]; then
    echo "  PASS: Module executed without errors"
else
    echo "  FAIL: Module execution failed"
    exit 1
fi

# Test 3: Module execution (root)
echo "Test 3: Root Execution"
sudo python3 linux_security_audit.py -m CUSTOM -f JSON -o /tmp/test_output.json
if [ $? -eq 0 ]; then
    echo "  PASS: Module executed as root"
else
    echo "  FAIL: Root execution failed"
    exit 1
fi

# Test 4: Validate JSON output
echo "Test 4: JSON Validation"
python3 -c "import json; json.load(open('/tmp/test_output.json'))"
if [ $? -eq 0 ]; then
    echo "  PASS: Valid JSON output"
else
    echo "  FAIL: Invalid JSON output"
    exit 1
fi

echo "=== All Tests Passed ==="

Manual Testing Checklist

Before Submitting PR:

  • Module runs standalone: python3 module_name.py
  • Module discovered: python3 linux_security_audit.py --list-modules
  • Module runs integrated: python3 linux_security_audit.py -m NAME
  • Works without root (graceful degradation)
  • Works with root (full functionality)
  • All output formats work (HTML, CSV, JSON, XML, Console)
  • Remediation commands are safe and correct
  • No hardcoded paths (use variables)
  • No security vulnerabilities (command injection, etc.)
  • Documentation is complete
  • Code follows style guidelines

Contributing

Contribution Workflow

  1. Discuss First (for major changes)

    • Open an issue to discuss proposed changes
    • Get feedback before investing significant time
  2. Fork and Branch

    git checkout -b feature/descriptive-name
  3. Make Changes

    • Follow code standards
    • Write tests
    • Update documentation
  4. Test Thoroughly

    • Run unit tests
    • Run integration tests
    • Manual testing on target systems
  5. Commit with Clear Messages

    git add .
    git commit -m "feat: Add XYZ security checks to module_name
    
    - Implement checks for ABC
    - Add remediation for DEF
    - Update documentation
    
    Closes #123"
  6. Push and Create PR

    git push origin feature/descriptive-name
    • Create pull request on GitHub
    • Fill out PR template completely
    • Link related issues
  7. Respond to Feedback

    • Address review comments
    • Update PR as needed
    • Be responsive to maintainers

Commit Message Guidelines

Format:

<type>: <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting)
  • refactor: Code refactoring
  • test: Test additions/changes
  • chore: Maintenance tasks

Examples:

feat: Add ISO27001 module with 145 security checks

Implements comprehensive ISO/IEC 27001:2022 Annex A controls
including organizational, people, physical, and technological
categories.

Closes #45

---

fix: Correct file permission check in Core module

The check was using wrong octal notation. Changed from 600 to 0o600
to properly represent octal value in Python 3.

Fixes #89

---

docs: Update Quick Start Guide with new examples

Added examples for:
- Multi-module execution
- Selective remediation workflow
- SIEM integration

Pull Request Guidelines

PR Title: Clear and descriptive

  • Add ENISA module with EU cybersecurity checks
  • Fix permission error in NIST module
  • Update documentation for remediation workflow

PR Description: Include:

## Description
Brief description of changes

## Type of Change
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update

## Changes Made
- Change 1
- Change 2
- Change 3

## Testing
Describe testing performed:
- Unit tests: Pass/Fail
- Integration tests: Pass/Fail
- Manual testing: Details

## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review performed
- [ ] Comments added for complex code
- [ ] Documentation updated
- [ ] No new warnings generated
- [ ] Tests added that prove fix is effective
- [ ] All tests pass locally

## Related Issues
Closes #123
Related to #456

Code Review Process

What Reviewers Look For:

  1. Functionality: Does it work as intended?
  2. Security: Any security implications?
  3. Code Quality: Follows standards?
  4. Documentation: Adequately documented?
  5. Testing: Sufficiently tested?
  6. Performance: Any performance issues?

Responding to Reviews:

  • Be respectful and professional
  • Ask questions if feedback is unclear
  • Make requested changes promptly
  • Explain decisions when disagreeing
  • Mark conversations as resolved

Advanced Topics

Performance Optimization

Avoid Redundant Checks:

# BAD: Multiple calls to same command
def check_multiple():
    run_command("systemctl list-units")  # Call 1
    run_command("systemctl list-units")  # Call 2 - redundant
    
# GOOD: Call once, reuse result
def check_multiple():
    returncode, stdout, stderr = run_command("systemctl list-units")
    # Parse stdout multiple times instead

Cache Expensive Operations:

# Module-level cache
_os_info_cache = None

def detect_os() -> Dict[str, str]:
    """Detect OS with caching"""
    global _os_info_cache
    
    if _os_info_cache is not None:
        return _os_info_cache
    
    # Expensive OS detection
    os_info = {}
    # ... detection logic ...
    
    _os_info_cache = os_info
    return os_info

Async/Parallel Execution (Future Enhancement)

Potential for Parallel Checks:

import concurrent.futures

def run_module_async(shared_data: Dict[str, Any]) -> List[AuditResult]:
    """Execute checks in parallel"""
    all_results = []
    
    check_functions = [
        check_category_1,
        check_category_2,
        check_category_3
    ]
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(func, shared_data) for func in check_functions]
        
        for future in concurrent.futures.as_completed(futures):
            try:
                results = future.result()
                all_results.extend(results)
            except Exception as e:
                print(f"Check failed: {e}")
    
    return all_results

Custom Output Formats

Adding New Export Format:

def export_custom_format(results: List[AuditResult], 
                        exec_info: ExecutionInfo,
                        output_path: str) -> Path:
    """
    Export results in custom format
    
    Args:
        results: List of audit results
        exec_info: Execution information
        output_path: Output file path
        
    Returns:
        Path to created file
    """
    # Implement custom format
    with open(output_path, 'w') as f:
        # Write custom format
        pass
    
    return Path(output_path)

Extending Remediation

Custom Remediation Handler:

def custom_remediation_handler(result: AuditResult) -> bool:
    """
    Custom remediation logic
    
    Args:
        result: AuditResult with remediation command
        
    Returns:
        True if successful
    """
    # Pre-remediation checks
    if not verify_safe(result.remediation):
        return False
    
    # Execute remediation
    try:
        subprocess.run(result.remediation, shell=True, check=True)
        return True
    except:
        return False

Additional Resources

Documentation

External Resources

Community

  • GitHub Issues: Report bugs and request features
  • Discussions: Ask questions and share ideas
  • Pull Requests: Contribute code and documentation

← Back to Framework Reference | Home | Next: Troubleshooting Guide →

Clone this wiki locally