-
Notifications
You must be signed in to change notification settings - Fork 2
Development Guide
This guide provides comprehensive information for developers who want to contribute to, extend, or customize the Linux Security Audit Project.
- Getting Started
- Development Environment
- Project Architecture
- Creating Security Modules
- Code Standards
- Testing Guidelines
- Contributing
- Advanced Topics
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
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/MacRecommended Tools:
# Code formatting
pip install black
# Linting
pip install pylint flake8
# Type checking
pip install mypy
# Testing
pip install pytest- 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- Create Development Branch:
git checkout -b feature/your-feature-name
# or
git checkout -b bugfix/issue-description- Verify Setup:
# List available modules
python3 linux_security_audit.py --list-modules
# Run basic test
python3 linux_security_audit.py -m Core -f ConsoleLinux-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
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=1Recommended 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┌─────────────────────────────────────────────────────────────┐
│ 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│
└───────────────┘ └──────────┘ └──────────┘ └──────────┘
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 logicValid Status Values:
-
Pass: Check passed successfully -
Fail: Security issue detected -
Warning: Potential security concern -
Info: Informational finding -
Error: Check could not be completed
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: intPurpose: 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
}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-
Scan Directory: Orchestrator scans for
module_*.pyfiles -
Dynamic Import: Imports module using
importlib -
Validate Interface: Checks for
run_module()function - Register Module: Adds to available modules dictionary
-
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-
Plan Module Scope
- Define security framework/standard
- Identify check categories
- List specific security controls
- Determine OS-specific requirements
-
Create Module File
- Name:
module_<name>.py - Add module header with documentation
- Implement required interface
- Name:
-
Develop Check Functions
- Create category-specific check functions
- Return AuditResult objects
- Handle errors gracefully
-
Test Module
- Standalone testing
- Integration testing
- Root and non-root testing
-
Document Module
- Update module documentation
- Add to README
- Document check details
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)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 resultsdef 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 resultsdef 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 resultsdef 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 resultsdef 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 resultsFollow 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
- Functions/variables:
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"""
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
"""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# 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 resultsSpecific 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
passNever 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)}")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()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 ==="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
-
Discuss First (for major changes)
- Open an issue to discuss proposed changes
- Get feedback before investing significant time
-
Fork and Branch
git checkout -b feature/descriptive-name
-
Make Changes
- Follow code standards
- Write tests
- Update documentation
-
Test Thoroughly
- Run unit tests
- Run integration tests
- Manual testing on target systems
-
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"
-
Push and Create PR
git push origin feature/descriptive-name
- Create pull request on GitHub
- Fill out PR template completely
- Link related issues
-
Respond to Feedback
- Address review comments
- Update PR as needed
- Be responsive to maintainers
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
PR Title: Clear and descriptive
Add ENISA module with EU cybersecurity checksFix permission error in NIST moduleUpdate 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 #456What Reviewers Look For:
- Functionality: Does it work as intended?
- Security: Any security implications?
- Code Quality: Follows standards?
- Documentation: Adequately documented?
- Testing: Sufficiently tested?
- 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
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 insteadCache 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_infoPotential 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_resultsAdding 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)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- Module Documentation: Detailed module reference
- Framework Reference: Security framework details
- Usage Guide: Using the audit tool
- Troubleshooting: Common issues
- Python Documentation: https://docs.python.org/3/
- PEP 8 Style Guide: https://peps.python.org/pep-0008/
- Linux Security: https://www.kernel.org/doc/html/latest/security/
- Security Standards: See Framework Reference
- 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 →