484 lines
15 KiB
Bash
Executable File
484 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ===============================================================================
|
|
# Linux System Audit Deployment Script
|
|
# This script helps deploy and run the audit across multiple devices
|
|
# Version: 2.0 - Enhanced with better error handling and security features
|
|
# ===============================================================================
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
INVENTORY_FILE="$SCRIPT_DIR/inventory.ini"
|
|
PLAYBOOK_FILE="$SCRIPT_DIR/linux_audit_playbook.yml"
|
|
AUDIT_SCRIPT="$SCRIPT_DIR/linux_system_audit.sh"
|
|
RESULTS_DIR="$SCRIPT_DIR/audit_results"
|
|
|
|
# Enhanced error handling
|
|
error_handler() {
|
|
local exit_code=$1
|
|
local line_no=$2
|
|
local last_command=$3
|
|
|
|
echo -e "${RED}Error occurred in: $last_command${NC}"
|
|
echo -e "${RED}Exit code: $exit_code${NC}"
|
|
echo -e "${RED}Line number: $line_no${NC}"
|
|
exit $exit_code
|
|
}
|
|
|
|
# Set up error trap
|
|
trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
print_status() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
check_dependencies() {
|
|
print_status "Checking dependencies..."
|
|
|
|
local missing_deps=()
|
|
local optional_deps=()
|
|
|
|
# Required dependencies
|
|
if ! command -v ansible >/dev/null 2>&1; then
|
|
missing_deps+=("ansible")
|
|
fi
|
|
|
|
if ! command -v ansible-playbook >/dev/null 2>&1; then
|
|
missing_deps+=("ansible-playbook")
|
|
fi
|
|
|
|
# Optional but recommended dependencies
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
optional_deps+=("jq")
|
|
fi
|
|
|
|
if ! command -v tar >/dev/null 2>&1; then
|
|
optional_deps+=("tar")
|
|
fi
|
|
|
|
if [ ${#missing_deps[@]} -ne 0 ]; then
|
|
print_error "Missing required dependencies: ${missing_deps[*]}"
|
|
echo "Please install Ansible:"
|
|
echo " Ubuntu/Debian: sudo apt update && sudo apt install ansible"
|
|
echo " Fedora: sudo dnf install ansible"
|
|
echo " Or via pip: pip3 install ansible"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ${#optional_deps[@]} -ne 0 ]; then
|
|
print_warning "Missing optional dependencies: ${optional_deps[*]}"
|
|
echo "These are recommended but not required:"
|
|
echo " jq: For enhanced JSON processing"
|
|
echo " tar: For result compression"
|
|
fi
|
|
|
|
print_success "All required dependencies found"
|
|
|
|
# Check Ansible version
|
|
local ansible_version=$(ansible --version | head -1 | awk '{print $2}')
|
|
print_status "Ansible version: $ansible_version"
|
|
}
|
|
|
|
check_files() {
|
|
print_status "Checking required files..."
|
|
|
|
local missing_files=()
|
|
|
|
if [ ! -f "$INVENTORY_FILE" ]; then
|
|
missing_files+=("$INVENTORY_FILE")
|
|
fi
|
|
|
|
if [ ! -f "$PLAYBOOK_FILE" ]; then
|
|
missing_files+=("$PLAYBOOK_FILE")
|
|
fi
|
|
|
|
if [ ! -f "$AUDIT_SCRIPT" ]; then
|
|
missing_files+=("$AUDIT_SCRIPT")
|
|
fi
|
|
|
|
if [ ${#missing_files[@]} -ne 0 ]; then
|
|
print_error "Missing files: ${missing_files[*]}"
|
|
exit 1
|
|
fi
|
|
|
|
print_success "All required files found"
|
|
}
|
|
|
|
test_connectivity() {
|
|
print_status "Testing connectivity to hosts..."
|
|
|
|
# Test connectivity with detailed output
|
|
local connectivity_test=$(ansible all -i "$INVENTORY_FILE" -m ping --one-line 2>&1)
|
|
local success_count=$(echo "$connectivity_test" | grep -c "SUCCESS" || echo "0")
|
|
local total_hosts=$(ansible all -i "$INVENTORY_FILE" --list-hosts 2>/dev/null | grep -v "hosts" | wc -l)
|
|
|
|
print_status "Connectivity test results: $success_count/$total_hosts hosts reachable"
|
|
|
|
if [ "$success_count" -eq "$total_hosts" ]; then
|
|
print_success "Successfully connected to all hosts"
|
|
elif [ "$success_count" -gt 0 ]; then
|
|
print_warning "Connected to $success_count/$total_hosts hosts"
|
|
echo "Some hosts may not be reachable. Check your inventory file and SSH keys."
|
|
echo "Failed connections:"
|
|
echo "$connectivity_test" | grep -v "SUCCESS" || true
|
|
|
|
read -p "Continue with available hosts? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
exit 1
|
|
fi
|
|
else
|
|
print_error "No hosts are reachable. Please check:"
|
|
echo " 1. SSH key authentication is set up"
|
|
echo " 2. Inventory file is correct"
|
|
echo " 3. Hosts are online and accessible"
|
|
echo " 4. Firewall rules allow SSH connections"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
run_audit() {
|
|
print_status "Starting audit across all hosts..."
|
|
|
|
# Create results directory
|
|
mkdir -p "$RESULTS_DIR"
|
|
|
|
# Run the playbook
|
|
if ansible-playbook -i "$INVENTORY_FILE" "$PLAYBOOK_FILE" -v; then
|
|
print_success "Audit completed successfully"
|
|
else
|
|
print_error "Audit failed. Check the output above for details."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
generate_summary_report() {
|
|
print_status "Generating summary report..."
|
|
|
|
local summary_file="$RESULTS_DIR/MASTER_SUMMARY_$(date +%Y%m%d_%H%M%S).txt"
|
|
|
|
cat > "$summary_file" << EOF
|
|
===============================================================================
|
|
COMPREHENSIVE LINUX SYSTEM AUDIT SUMMARY REPORT
|
|
Generated: $(date)
|
|
Script Version: 1.0
|
|
===============================================================================
|
|
|
|
OVERVIEW:
|
|
---------
|
|
EOF
|
|
|
|
# Count hosts
|
|
local total_hosts=$(find "$RESULTS_DIR" -maxdepth 1 -type d | grep -v "^$RESULTS_DIR$" | wc -l)
|
|
echo "Total hosts audited: $total_hosts" >> "$summary_file"
|
|
echo "" >> "$summary_file"
|
|
|
|
# Process each host
|
|
for host_dir in "$RESULTS_DIR"/*; do
|
|
if [ -d "$host_dir" ] && [ "$(basename "$host_dir")" != "audit_results" ]; then
|
|
local hostname=$(basename "$host_dir")
|
|
echo "=== $hostname ===" >> "$summary_file"
|
|
|
|
if [ -f "$host_dir/SUMMARY.txt" ]; then
|
|
cat "$host_dir/SUMMARY.txt" >> "$summary_file"
|
|
else
|
|
echo "No summary file found" >> "$summary_file"
|
|
fi
|
|
echo "" >> "$summary_file"
|
|
fi
|
|
done
|
|
|
|
print_success "Summary report generated: $summary_file"
|
|
}
|
|
|
|
create_dashboard() {
|
|
print_status "Creating dynamic HTML dashboard..."
|
|
|
|
local dashboard_file="$RESULTS_DIR/dashboard.html"
|
|
local all_results_file="$RESULTS_DIR/all_results.json"
|
|
|
|
# Aggregate all JSON results into a single file
|
|
echo "[" > "$all_results_file"
|
|
first=true
|
|
for host_dir in "$RESULTS_DIR"/*; do
|
|
if [ -d "$host_dir" ]; then
|
|
local json_file="$host_dir/results.json"
|
|
if [ -f "$json_file" ]; then
|
|
if [ "$first" = false ]; then
|
|
echo "," >> "$all_results_file"
|
|
fi
|
|
cat "$json_file" >> "$all_results_file"
|
|
first=false
|
|
fi
|
|
fi
|
|
done
|
|
echo "]" >> "$all_results_file"
|
|
|
|
cat > "$dashboard_file" << 'EOF'
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Linux System Audit Dashboard</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; background-color: #f0f2f5; }
|
|
.header { background-color: #fff; padding: 20px; border-bottom: 1px solid #ddd; text-align: center; }
|
|
.header h1 { margin: 0; }
|
|
.container { display: flex; flex-wrap: wrap; padding: 20px; justify-content: center; }
|
|
.card { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px; padding: 20px; width: 300px; }
|
|
.card h2 { margin-top: 0; color: #333; border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
|
.card p { color: #666; }
|
|
.card .label { font-weight: bold; color: #333; }
|
|
.status-ok { color: #28a745; }
|
|
.status-warning { color: #ffc107; }
|
|
.status-error { color: #dc3545; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>Linux System Audit Dashboard</h1>
|
|
<p id="generation-date"></p>
|
|
</div>
|
|
|
|
<div id="hosts-container" class="container">
|
|
<!-- Host information will be inserted here -->
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.getElementById('generation-date').textContent = `Generated: ${new Date().toLocaleString()}`;
|
|
|
|
fetch('all_results.json')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const container = document.getElementById('hosts-container');
|
|
data.forEach(host => {
|
|
const card = document.createElement('div');
|
|
card.className = 'card';
|
|
|
|
let ufwStatusClass = 'status-ok';
|
|
if (host.security.ufw_status !== 'active') {
|
|
ufwStatusClass = 'status-warning';
|
|
}
|
|
|
|
let rootLoginClass = 'status-ok';
|
|
if (host.security.ssh_root_login.toLowerCase() !== 'no') {
|
|
rootLoginClass = 'status-error';
|
|
}
|
|
|
|
card.innerHTML = `
|
|
<h2>${host.system.hostname}</h2>
|
|
<p><span class="label">OS:</span> ${host.system.os}</p>
|
|
<p><span class="label">Kernel:</span> ${host.system.kernel}</p>
|
|
<p><span class="label">IP Address:</span> ${host.system.ip_addresses}</p>
|
|
<p><span class="label">Uptime:</span> ${host.system.uptime}</p>
|
|
<p><span class="label">Running Containers:</span> ${host.containers.running_containers}</p>
|
|
<p><span class="label">UFW Status:</span> <span class="${ufwStatusClass}">${host.security.ufw_status}</span></p>
|
|
<p><span class="label">SSH Root Login:</span> <span class="${rootLoginClass}">${host.security.ssh_root_login}</span></p>
|
|
<p><span class="label">Open Ports:</span> ${host.security.open_ports.join(', ')}</p>
|
|
`;
|
|
container.appendChild(card);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading audit data:', error);
|
|
const container = document.getElementById('hosts-container');
|
|
container.innerHTML = '<p style="color: red;">Could not load audit data. Make sure all_results.json is available.</p>';
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
EOF
|
|
|
|
print_success "HTML dashboard created: $dashboard_file"
|
|
}
|
|
|
|
cleanup_old_results() {
|
|
if [ -d "$RESULTS_DIR" ]; then
|
|
print_status "Cleaning up old results..."
|
|
rm -rf "$RESULTS_DIR"/*
|
|
print_success "Old results cleaned"
|
|
fi
|
|
}
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
Linux System Audit Deployment Script
|
|
|
|
Usage: $0 [OPTIONS]
|
|
|
|
OPTIONS:
|
|
-h, --help Show this help message
|
|
-c, --check Check dependencies and connectivity only
|
|
-n, --no-cleanup Don't cleanup old results
|
|
-q, --quick Skip connectivity test
|
|
--inventory FILE Use custom inventory file
|
|
--results-dir DIR Use custom results directory
|
|
--setup-sudo Set up passwordless sudo on all hosts in inventory
|
|
|
|
EXAMPLES:
|
|
$0 Run full audit
|
|
$0 -c Check setup only
|
|
$0 --setup-sudo Set up passwordless sudo
|
|
$0 --inventory /path/to/custom/inventory.ini
|
|
|
|
Before running:
|
|
1. Edit inventory.ini with your server details
|
|
2. Ensure SSH key authentication is set up
|
|
3. Verify sudo access on target hosts
|
|
EOF
|
|
}
|
|
|
|
setup_passwordless_sudo() {
|
|
print_status "Setting up passwordless sudo for all hosts in inventory..."
|
|
|
|
if [ ! -f "$INVENTORY_FILE" ]; then
|
|
print_error "Inventory file not found at $INVENTORY_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
# Extract user and host from inventory, ignore comments and empty lines
|
|
mapfile -t hosts < <(grep -vE '^\s*(#|$|\[)' "$INVENTORY_FILE" | grep 'ansible_host' | awk '{print $1, $2}' | sed 's/ansible_host=//')
|
|
|
|
if [ ${#hosts[@]} -eq 0 ]; then
|
|
print_error "No hosts found in inventory file."
|
|
exit 1
|
|
fi
|
|
|
|
for host_info in "${hosts[@]}"; do
|
|
read -r alias host <<< "$host_info"
|
|
user_host=$(awk -v alias="$alias" '$1 == alias {print $3}' "$INVENTORY_FILE" | sed 's/ansible_user=//')
|
|
|
|
if [[ -z "$user_host" ]]; then
|
|
# Fallback for different inventory formats
|
|
user_host=$(grep "$host" "$INVENTORY_FILE" | grep "ansible_user" | sed 's/.*ansible_user=\([^ ]*\).*/\1@'${host}'/')
|
|
user_host=${user_host%@*}
|
|
fi
|
|
|
|
if [[ -z "$user_host" ]]; then
|
|
print_warning "Could not determine user for host $alias. Skipping."
|
|
continue
|
|
fi
|
|
|
|
user=$(echo "$user_host" | cut -d'@' -f1)
|
|
|
|
print_status "Configuring $alias ($user@$host)..."
|
|
|
|
if [ "$user" = "root" ]; then
|
|
print_success "Skipping $alias - already root user"
|
|
continue
|
|
fi
|
|
|
|
# Create sudoers file for the user
|
|
if ssh "$user@$host" "echo '$user ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/audit-$user && sudo chmod 440 /etc/sudoers.d/audit-$user"; then
|
|
print_success "Successfully configured passwordless sudo for $alias"
|
|
else
|
|
print_error "Failed to configure passwordless sudo for $alias"
|
|
fi
|
|
done
|
|
|
|
print_success "Passwordless sudo setup complete!"
|
|
}
|
|
|
|
main() {
|
|
local cleanup=true
|
|
local check_only=false
|
|
local quick=false
|
|
local setup_sudo=false
|
|
|
|
# Parse command line arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-c|--check)
|
|
check_only=true
|
|
shift
|
|
;;
|
|
-n|--no-cleanup)
|
|
cleanup=false
|
|
shift
|
|
;;
|
|
-q|--quick)
|
|
quick=true
|
|
shift
|
|
;;
|
|
--inventory)
|
|
INVENTORY_FILE="$2"
|
|
shift 2
|
|
;;
|
|
--results-dir)
|
|
RESULTS_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--setup-sudo)
|
|
setup_sudo=true
|
|
shift
|
|
;;
|
|
*)
|
|
print_error "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$setup_sudo" = true ]; then
|
|
setup_passwordless_sudo
|
|
exit 0
|
|
fi
|
|
|
|
print_status "Starting Linux System Audit Deployment"
|
|
|
|
check_dependencies
|
|
check_files
|
|
|
|
if [ "$check_only" = true ]; then
|
|
test_connectivity
|
|
print_success "All checks passed!"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$cleanup" = true ]; then
|
|
cleanup_old_results
|
|
fi
|
|
|
|
if [ "$quick" = false ]; then
|
|
test_connectivity
|
|
fi
|
|
|
|
run_audit
|
|
generate_summary_report
|
|
create_dashboard
|
|
|
|
print_success "Audit deployment completed!"
|
|
echo ""
|
|
echo "Results available in: $RESULTS_DIR"
|
|
echo "Open dashboard.html in your browser for a visual overview"
|
|
}
|
|
|
|
# Run main function with all arguments
|
|
main "$@"
|