Users Online

· Guests Online: 17

· Members Online: 0

· Total Members: 285
· Newest Member: Zarfdrilhor

Forum Threads

Newest Threads
No Threads created
Hottest Threads
No Threads created

Latest Articles

Website Blocking — Enterprise Implementation

Website Blocking — Enterprise Implementation

 

Architecture Options

Method 1: Local Hosts File        → Per machine, simple

Method 2: Group Policy / DNS      → Domain-wide, recommended

Method 3: Proxy Server (Squid)    → Full content filtering

Method 4: DNS Sinkhole (Pi-hole)  → Network-wide blocking

Method 5: Firewall / iptables     → Deep packet level

 

 

 

Method 1: Hosts File via GPO (Domain-Wide)

 

powershell

# Script: Deploy-WebBlocking.ps1

# Push blocked sites to all domain machines via hosts file

 

param(

    [string]$BlockListFile = "C:\Scripts\blocked_sites.txt",

    [string]$HostsFile     = "C:\Windows\System32\drivers\etc\hosts",

    [string]$LogFile       = "C:\Logs\WebBlock.log"

)

 

# ── Blocked Sites List ──────────────────────────────────────────────

$blockedSites = @(

    # Personal Email

    "gmail.com", "www.gmail.com", "mail.google.com",

    "outlook.live.com", "hotmail.com", "yahoo.com", "mail.yahoo.com",

    "protonmail.com", "tutanota.com",

 

    # Social Media

    "facebook.com", "www.facebook.com",

    "twitter.com", "x.com",

    "instagram.com", "tiktok.com",

    "snapchat.com", "reddit.com",

    "linkedin.com",   # Remove if needed for business

 

    # File Sharing / Cloud Storage

    "wetransfer.com", "dropbox.com",

    "mega.nz", "mediafire.com",

    "pastebin.com", "paste.ee",

 

    # Streaming

    "youtube.com", "www.youtube.com",

    "netflix.com", "primevideo.com",

    "twitch.tv", "spotify.com",

 

    # Gaming

    "steam.com", "store.steampowered.com",

    "epicgames.com", "roblox.com"

)

 

function Write-Log($msg) {

    $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

    "$ts $msg" | Tee-Object -FilePath $LogFile -Append

}

 

# ── Backup original hosts file ──────────────────────────────────────

$backup = "$HostsFile.bak_$(Get-Date -Format 'yyyyMMdd')"

if (-not (Test-Path $backup)) {

    Copy-Item $HostsFile $backup

    Write-Log "Hosts backup: $backup"

}

 

# ── Read current hosts ──────────────────────────────────────────────

$currentHosts = Get-Content $HostsFile

 

# ── Remove old block entries ────────────────────────────────────────

$cleaned = $currentHosts | Where-Object { $_ -notmatch "# CORP-BLOCK" }

 

# ── Add new block entries ────────────────────────────────────────────

$newEntries = @("", "# ── CORPORATE WEB BLOCKS — DO NOT EDIT ── CORP-BLOCK-START ──")

foreach ($site in $blockedSites) {

    $newEntries += "0.0.0.0 $site  # CORP-BLOCK"

    $newEntries += "0.0.0.0 www.$site  # CORP-BLOCK"

}

$newEntries += "# ── CORP-BLOCK-END ──"

 

# ── Write final hosts file ───────────────────────────────────────────

($cleaned + $newEntries) | Set-Content $HostsFile -Encoding UTF8

Write-Log "Blocked $($blockedSites.Count) sites on $env:COMPUTERNAME"

 

# ── Flush DNS cache ──────────────────────────────────────────────────

ipconfig /flushdns | Out-Null

Write-Log "DNS cache flushed"

 

Write-Host "[✔] Web blocking applied: $($blockedSites.Count) sites blocked" -ForegroundColor Green

 

 

 

Method 2: Internet Explorer / Edge GPO Restrictions

Computer Configuration →

  Administrative Templates →

    Windows Components →

      Microsoft Edge →

        Block access to a list of URLs

 

Add URLs:

  gmail.com

  *facebook.com

  *twitter.com

  *youtube.com

 

Via Registry (push via GPO):


powershell

# Block sites in Edge/Chrome via registry

$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge\URLBlocklist"

if (-not (Test-Path $regPath)) {

    New-Item -Path $regPath -Force | Out-Null

}

 

$blockedURLs = @(

    "gmail.com", "facebook.com", "twitter.com",

    "instagram.com", "youtube.com", "tiktok.com",

    "dropbox.com", "wetransfer.com"

)

 

$i = 1

foreach ($url in $blockedURLs) {

    Set-ItemProperty -Path $regPath -Name "$i" -Value $url

    $i++

}

 

Write-Host "[✔] Edge URL blocklist applied via registry"

 

 

Method 3: Windows Firewall Rules

powershell

# Block by IP ranges (Gmail/Google IPs)

$googleIPs = @(

    "142.250.0.0/15", "172.217.0.0/16",

    "74.125.0.0/16",  "64.233.160.0/19",

    "66.249.64.0/19", "216.58.192.0/19"

)

 

foreach ($ip in $googleIPs) {

    $ruleName = "BLOCK-GOOGLE-$($ip -replace '[./]','-')"

    New-NetFirewallRule `

        -DisplayName $ruleName `

        -Direction   Outbound `

        -Action      Block `

        -RemoteAddress $ip `

        -Profile     Any `

        -Enabled     True | Out-Null

    Write-Host "[+] Blocked: $ip"

}

 

Write-Host "[✔] Firewall rules applied"

Part 2: Linux — Multiple Methods


Method 1: Hosts File Blocking

bash

#!/bin/bash

# deploy_web_blocking.sh — Linux hosts file blocker

 

HOSTS_FILE="/etc/hosts"

BACKUP="/etc/hosts.bak.$(date +%Y%m%d)"

LOG="/var/log/web-blocking.log"

 

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG"; }

 

# Blocked sites list

BLOCKED_SITES=(

    # Personal Email

    "gmail.com" "mail.google.com"

    "outlook.live.com" "hotmail.com"

    "yahoo.com" "mail.yahoo.com"

    "protonmail.com" "tutanota.com"

 

    # Social Media

    "facebook.com" "twitter.com" "x.com"

    "instagram.com" "tiktok.com"

    "snapchat.com" "reddit.com"

 

    # Cloud Storage / File Sharing

    "dropbox.com" "wetransfer.com"

    "mega.nz" "mediafire.com"

    "pastebin.com"

 

    # Streaming

    "youtube.com" "netflix.com"

    "twitch.tv" "spotify.com"

    "primevideo.com"

 

    # Gaming

    "steampowered.com" "epicgames.com"

    "roblox.com"

)

 

# Backup

[ ! -f "$BACKUP" ] && cp "$HOSTS_FILE" "$BACKUP" && log "Backup: $BACKUP"

 

# Remove old entries

sed -i '/# CORP-BLOCK/d' "$HOSTS_FILE"

sed -i '/CORP-BLOCK-START/,/CORP-BLOCK-END/d' "$HOSTS_FILE"

 

# Add new block entries

{

    echo ""

    echo "# ── CORPORATE WEB BLOCKS — DO NOT EDIT — CORP-BLOCK-START ──"

    for site in "${BLOCKED_SITES[@]}"; do

        echo "0.0.0.0 $site        # CORP-BLOCK"

        echo "0.0.0.0 www.$site    # CORP-BLOCK"

    done

    echo "# ── CORP-BLOCK-END ──"

} >> "$HOSTS_FILE"

 

# Flush DNS cache

systemd-resolve --flush-caches 2>/dev/null || \

    service nscd restart 2>/dev/null || \

    resolvectl flush-caches 2>/dev/null

 

log "Blocked ${#BLOCKED_SITES[@]} sites on $(hostname)"

echo "[✔] Web blocking applied: ${#BLOCKED_SITES[@]} sites blocked"

Method 2: Squid Proxy — Full Content Filtering (Recommended)

 

bash

# Install Squid

sudo apt install squid -y          # Ubuntu/Debian

sudo dnf install squid -y          # RHEL/Fedora

Main config — /etc/squid/squid.conf:

squid

# ── Squid Corporate Web Filter ──────────────────────────────────────

 

# Network ACLs

acl localnet src 192.168.0.0/16

acl localnet src 10.0.0.0/8

acl SSL_ports port 443

acl Safe_ports port 80 443 8080

acl CONNECT method CONNECT

 

# Block list files

acl blocked_sites   dstdomain "/etc/squid/blocked_domains.txt"

acl blocked_words   url_regex -i "/etc/squid/blocked_keywords.txt"

acl social_media    dstdomain "/etc/squid/social_media.txt"

acl personal_email  dstdomain "/etc/squid/personal_email.txt"

acl file_sharing    dstdomain "/etc/squid/file_sharing.txt"

 

# Time ACLs

acl work_hours time MTWHF 08:00-18:00

acl lunch_time time MTWHF 12:00-13:00

 

# ── DENY RULES ──────────────────────────────────────────────────────

http_access deny blocked_sites

http_access deny personal_email

http_access deny social_media

http_access deny !work_hours       # Block everything outside work hours

http_access deny file_sharing

 

# Allow local network during work hours

http_access allow localnet work_hours

http_access deny all

 

# Proxy settings

http_port 3128

https_port 3129 intercept ssl-bump \

    cert=/etc/squid/ssl/squid-ca.crt \

    key=/etc/squid/ssl/squid-ca.key

 

# SSL Bump for HTTPS inspection

ssl_bump splice all

 

# Logging

access_log /var/log/squid/access.log combined

cache_log  /var/log/squid/cache.log

 

# Block page

deny_info http://usb-monitor.company.com/blocked.html all

 

cache_mem 256 MB

maximum_object_size 10 MB

Blocked domains files:

bash

# /etc/squid/personal_email.txt

cat > /etc/squid/personal_email.txt <<EOF

gmail.com

mail.google.com

googlemail.com

outlook.live.com

hotmail.com

live.com

yahoo.com

mail.yahoo.com

ymail.com

protonmail.com

tutanota.com

zoho.com

guerrillamail.com

temp-mail.org

10minutemail.com

EOF

 

# /etc/squid/social_media.txt

cat > /etc/squid/social_media.txt <<EOF

facebook.com

fbcdn.net

twitter.com

t.co

x.com

instagram.com

cdninstagram.com

tiktok.com

tiktokcdn.com

snapchat.com

reddit.com

redd.it

pinterest.com

tumblr.com

EOF

 

# /etc/squid/file_sharing.txt

cat > /etc/squid/file_sharing.txt <<EOF

dropbox.com

dropboxusercontent.com

wetransfer.com

we.tl

mega.nz

mediafire.com

rapidshare.com

zippyshare.com

pastebin.com

hastebin.com

EOF

bash

# Start Squid

sudo squid -k parse     # Test config

sudo systemctl enable --now squid

sudo systemctl status squid

 

 

 

Method 3: DNS Sinkhole (Network-Wide)

bash

# Install dnsmasq

sudo apt install dnsmasq -y

 

# Add block entries

cat >> /etc/dnsmasq.conf <<EOF

 

# Corporate Web Blocks

address=/gmail.com/0.0.0.0

address=/mail.google.com/0.0.0.0

address=/facebook.com/0.0.0.0

address=/twitter.com/0.0.0.0

address=/x.com/0.0.0.0

address=/instagram.com/0.0.0.0

address=/tiktok.com/0.0.0.0

address=/youtube.com/0.0.0.0

address=/dropbox.com/0.0.0.0

address=/wetransfer.com/0.0.0.0

address=/reddit.com/0.0.0.0

address=/snapchat.com/0.0.0.0

address=/netflix.com/0.0.0.0

address=/spotify.com/0.0.0.0

EOF

 

sudo systemctl restart dnsmasq

 

 

 

Method 4: iptables Firewall Rules

 

 

bash

#!/bin/bash

# Block websites at firewall level

 

# Flush existing web-block rules

iptables -D OUTPUT -j WEB_BLOCK 2>/dev/null

iptables -F WEB_BLOCK 2>/dev/null

iptables -X WEB_BLOCK 2>/dev/null

 

# Create new chain

iptables -N WEB_BLOCK

iptables -I OUTPUT -j WEB_BLOCK

 

# Resolve and block domains

DOMAINS=(

    "gmail.com" "facebook.com" "twitter.com"

    "instagram.com" "tiktok.com" "youtube.com"

    "dropbox.com" "wetransfer.com" "reddit.com"

)

 

for domain in "${DOMAINS[@]}"; do

    IPS=$(dig +short "$domain" | grep -E '^[0-9]+\.')

    for ip in $IPS; do

        iptables -A WEB_BLOCK -d "$ip" -j DROP

        echo "[+] Blocked $domain → $ip"

    done

done

 

# Save rules

iptables-save > /etc/iptables/rules.v4

echo "[✔] Firewall web blocking applied"

 

 

 

 

 

Part 3: Centralized Block List Manager

Save as /opt/web-blocker/manage_blocklist.py:

 


python

#!/usr/bin/env python3

"""

Centralized Web Block List Manager

Manage, deploy, and audit website blocking across all machines

"""

 

import json, os, subprocess, paramiko

from datetime import datetime

 

CONFIG_FILE  = "/opt/web-blocker/config.json"

REPORT_FILE  = "/var/www/html/usb-dashboard/webblocker.html"

 

# ── Default block categories ─────────────────────────────────────────

DEFAULT_CATEGORIES = {

    "personal_email": {

        "enabled": True,

        "sites": ["gmail.com","mail.google.com","hotmail.com",

                  "yahoo.com","protonmail.com","tutanota.com"]

    },

    "social_media": {

        "enabled": True,

        "sites": ["facebook.com","twitter.com","x.com",

                  "instagram.com","tiktok.com","snapchat.com","reddit.com"]

    },

    "file_sharing": {

        "enabled": True,

        "sites": ["dropbox.com","wetransfer.com","mega.nz",

                  "mediafire.com","pastebin.com"]

    },

    "streaming": {

        "enabled": True,

        "sites": ["youtube.com","netflix.com","twitch.tv",

                  "spotify.com","primevideo.com"]

    },

    "gaming": {

        "enabled": False,

        "sites": ["steampowered.com","epicgames.com","roblox.com"]

    }

}

 

def load_config():

    if os.path.exists(CONFIG_FILE):

        with open(CONFIG_FILE) as f:

            return json.load(f)

    return DEFAULT_CATEGORIES

 

def save_config(config):

    os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)

    with open(CONFIG_FILE, "w") as f:

        json.dump(config, f, indent=2)

 

def get_all_blocked_sites(config):

    sites = []

    for cat, data in config.items():

        if data["enabled"]:

            sites.extend(data["sites"])

    return sorted(set(sites))

 

def deploy_to_linux(host, ssh_key, blocked_sites):

    """Deploy block list to Linux machine via SSH"""

    try:

        client = paramiko.SSHClient()

        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        client.connect(host, key_filename=ssh_key, timeout=10)

 

        # Build hosts entries

        entries = "\n".join([

            f"0.0.0.0 {s}     # CORP-BLOCK\n0.0.0.0 www.{s}  # CORP-BLOCK"

            for s in blocked_sites

        ])

 

        cmd = f"""

        sed -i '/# CORP-BLOCK/d' /etc/hosts

        echo '# CORP-BLOCK-START' >> /etc/hosts

        echo '{entries}' >> /etc/hosts

        echo '# CORP-BLOCK-END' >> /etc/hosts

        systemd-resolve --flush-caches 2>/dev/null || true

        echo "OK"

        """

        _, stdout, _ = client.exec_command(cmd)

        result = stdout.read().decode().strip()

        client.close()

        return result == "OK"

    except Exception as e:

        print(f"[ERROR] {host}: {e}")

        return False

 

def generate_status_page(config, machines):

    cats_html = ""

    for cat, data in config.items():

        status_color = "#2ecc71" if data["enabled"] else "#e74c3c"

        status_text  = "ACTIVE" if data["enabled"] else "DISABLED"

        sites_html   = "".join(f"<span style='background:#2c2f3e;padding:2px 8px;"

                               f"border-radius:4px;font-size:0.8em;margin:2px'>{s}</span>"

                               for s in data["sites"])

        cats_html += f"""

        <div style='background:#1a1d27;border-radius:8px;padding:16px;margin-bottom:12px;

                    border-left:4px solid {status_color}'>

          <div style='display:flex;justify-content:space-between;align-items:center'>

            <strong style='text-transform:capitalize'>{cat.replace("_"," ")}</strong>

            <span style='background:{status_color};color:white;padding:2px 10px;

                  border-radius:12px;font-size:0.8em'>{status_text}</span>

          </div>

          <div style='margin-top:10px;display:flex;flex-wrap:wrap;gap:4px'>{sites_html}</div>

        </div>"""

 

    total_blocked = len(get_all_blocked_sites(config))

    html = f"""<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<meta http-equiv="refresh" content="300">

<title>Web Blocker Status</title>

<style>

  body {{ font-family:Segoe UI,Ubuntu,sans-serif;background:#0f1117;

         color:#e0e0e0;padding:30px; }}

  h1   {{ color:#e74c3c; }}

  .summary {{ display:flex;gap:16px;margin:20px 0;flex-wrap:wrap; }}

  .card {{ background:#1a1d27;border-radius:10px;padding:18px 24px;

           border-left:4px solid #e74c3c;min-width:130px;text-align:center; }}

  .card h3 {{ font-size:2em;color:#e74c3c;margin:0; }}

  .card p  {{ color:#888;font-size:0.85em;margin:4px 0 0; }}

</style>

</head>

<body>

<h1>🚫 Web Blocking Status</h1>

<p style='color:#666'>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>

<div class="summary">

  <div class="card"><h3>{total_blocked}</h3><p>Sites Blocked</p></div>

  <div class="card"><h3>{len(machines)}</h3><p>Machines</p></div>

  <div class="card"><h3>{sum(1 for c in config.values() if c['enabled'])}</h3><p>Active Categories</p></div>

</div>

<h2 style='color:#e74c3c;margin:20px 0 12px'>Block Categories</h2>

{cats_html}

</body>

</html>"""

 

    os.makedirs(os.path.dirname(REPORT_FILE), exist_ok=True)

    with open(REPORT_FILE, "w") as f:

        f.write(html)

    print(f"[✔] Status page: {REPORT_FILE}")

 

if __name__ == "__main__":

    config = load_config()

    sites  = get_all_blocked_sites(config)

    print(f"[*] Total blocked sites: {len(sites)}")

    machines = ["192.168.1.10", "192.168.1.11"]  # Add your machines

    generate_status_page(config, machines)

    save_config(config)

 

 

Deployment Summary

bash

# Linux — deploy immediately

chmod +x /opt/web-blocker/deploy_web_blocking.sh

sudo /opt/web-blocker/deploy_web_blocking.sh

 

# Push to all Linux machines via Ansible

ansible all -m script -a "/opt/web-blocker/deploy_web_blocking.sh" \

    --become -i /etc/ansible/hosts

 

# Windows — push via GPO startup script

# Place USB-WebBlock.ps1 in SYSVOL and link to GPO

 

# Schedule daily refresh (block lists update)

echo "0 6 * * * root /opt/web-blocker/deploy_web_blocking.sh" \

    >> /etc/crontab


Quick Reference — Methods vs Scale

Method

Scale

Bypass Risk

Best For

Hosts file

Per machine

Medium

Small teams

GPO + Registry

Domain-wide

Medium

Windows AD

Squid Proxy

Network-wide

Low

Enterprises

DNS Sinkhole

Network-wide

Medium

All devices

iptables/Firewall

Network-wide

Low

Linux-heavy

Commercial DLP

Enterprise

Very Low

Large orgs

 

 

 

 

 

 

 

 

 

 

 

custom block page (branded 403 page employees see), bypass request workflow (employees request access), or per-department policies (e.g. Marketing can access social media)?

 

 

Part 1: Custom Branded Block Page

Save as /var/www/html/blocked/index.html:

html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Access Blocked — Company IT Policy</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body {
    font-family: Segoe UI, Ubuntu, sans-serif;
    background: #0f1117;
    color: #e0e0e0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 20px;
  }
  .container {
    background: #1a1d27;
    border-radius: 16px;
    padding: 40px;
    max-width: 580px;
    width: 100%;
    text-align: center;
    border-top: 4px solid #e74c3c;
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  }
  .icon { font-size: 4em; margin-bottom: 16px; }
  .logo {
    font-size: 1em;
    color: #666;
    margin-bottom: 24px;
    letter-spacing: 2px;
    text-transform: uppercase;
  }
  h1 { color: #e74c3c; font-size: 1.6em; margin-bottom: 10px; }
  p  { color: #888; font-size: 0.95em; line-height: 1.7; margin-bottom: 20px; }
  .site-box {
    background: #0f1117;
    border: 1px solid #2c2f3e;
    border-radius: 8px;
    padding: 12px 20px;
    font-family: monospace;
    font-size: 1em;
    color: #e74c3c;
    margin-bottom: 24px;
    word-break: break-all;
  }
  .reason {
    background: #2c2f3e;
    border-radius: 8px;
    padding: 12px 16px;
    font-size: 0.85em;
    color: #aaa;
    margin-bottom: 28px;
    text-align: left;
  }
  .reason strong { color: #e0e0e0; }
  .btn {
    display: inline-block;
    padding: 12px 28px;
    border-radius: 8px;
    font-size: 0.95em;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    border: none;
    transition: opacity 0.2s;
    margin: 6px;
  }
  .btn:hover { opacity: 0.85; }
  .btn-primary { background: #e74c3c; color: white; }
  .btn-secondary {
    background: transparent;
    color: #7fb3d3;
    border: 1px solid #2c2f3e;
  }
  .divider {
    border: none;
    border-top: 1px solid #2c2f3e;
    margin: 28px 0;
  }
  .info-row {
    display: flex;
    justify-content: space-between;
    font-size: 0.78em;
    color: #555;
    flex-wrap: wrap;
    gap: 6px;
  }
  .modal-overlay {
    display: none;
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.7);
    z-index: 100;
    align-items: center;
    justify-content: center;
  }
  .modal-overlay.active { display: flex; }
  .modal {
    background: #1a1d27;
    border-radius: 14px;
    padding: 32px;
    max-width: 480px;
    width: 90%;
    border-top: 3px solid #3498db;
  }
  .modal h2 { color: #3498db; margin-bottom: 16px; font-size: 1.2em; }
  .modal input, .modal select, .modal textarea {
    width: 100%;
    background: #0f1117;
    border: 1px solid #2c2f3e;
    border-radius: 8px;
    color: #e0e0e0;
    padding: 10px 14px;
    font-size: 0.9em;
    margin-bottom: 14px;
    font-family: inherit;
    outline: none;
  }
  .modal textarea { height: 90px; resize: vertical; }
  .modal input:focus, .modal select:focus, .modal textarea:focus {
    border-color: #3498db;
  }
  .modal label { font-size: 0.82em; color: #888; display: block; margin-bottom: 5px; }
  .success-msg {
    display: none;
    background: #1a3a2a;
    border: 1px solid #2ecc71;
    border-radius: 8px;
    padding: 16px;
    color: #2ecc71;
    font-size: 0.9em;
    margin-top: 12px;
  }
</style>
</head>
<body>
 
<div class="container">
  <div class="icon">🚫</div>
  <div class="logo"> Company IT Security</div>
  <h1>Access Blocked</h1>
  <p>This website has been blocked by your organization's IT security policy.</p>
 
  <div class="site-box" id="blocked-url">
    <!-- Filled by JS or Squid -->
    <span id="url-display">This website</span>
  </div>
 
  <div class="reason">
    <strong>Reason:</strong>
    <span id="block-reason">Personal email / unauthorized web service</span><br><br>
    <strong>Policy:</strong> IT Security Policy v2.4 — Acceptable Use of Internet Resources<br>
    <strong>Category:</strong> <span id="block-category">Personal Communication</span>
  </div>
 
  <div>
    <button class="btn btn-primary" onclick="openRequestModal()">
      🔓 Request Access
    </button>
    <a class="btn btn-secondary" href="javascript:history.back()">
      ← Go Back
    </a>
  </div>
 
  <hr class="divider">
 
  <div class="info-row">
    <span>🖥 Host: <span id="hostname-display">WORKSTATION</span></span>
    <span>👤 User: <span id="user-display">employee</span></span>
    <span>🕐 <span id="time-display"></span></span>
    <span>📞 IT Help: ext. 1234</span>
  </div>
</div>
 
<!-- Access Request Modal -->
<div class="modal-overlay" id="requestModal">
  <div class="modal">
    <h2>🔓 Request Website Access</h2>
 
    <label>Your Name</label>
    <input type="text" id="req-name" placeholder="Full Name">
 
    <label>Employee ID</label>
    <input type="text" id="req-empid" placeholder="EMP-XXXXX">
 
    <label>Department</label>
    <select id="req-dept">
      <option value="">Select Department</option>
      <option>Engineering</option>
      <option>Marketing</option>
      <option>Finance</option>
      <option>HR</option>
      <option>Sales</option>
      <option>Operations</option>
      <option>Executive</option>
    </select>
 
    <label>Website you need access to</label>
    <input type="text" id="req-site" placeholder="e.g. gmail.com">
 
    <label>Business Justification</label>
    <textarea id="req-reason"
      placeholder="Why do you need access? How does it relate to your work?"></textarea>
 
    <label>Manager Email (for approval)</label>
    <input type="email" id="req-manager" placeholder="manager@company.com">
 
    <div style="display:flex;gap:10px;margin-top:6px">
      <button class="btn btn-primary" style="flex:1" onclick="submitRequest()">
        📨 Submit Request
      </button>
      <button class="btn btn-secondary" onclick="closeRequestModal()">
        Cancel
      </button>
    </div>
 
    <div class="success-msg" id="success-msg">
       Request submitted! Your manager and IT will review within 24 hours.
      Reference ID: <strong id="ref-id"></strong>
    </div>
  </div>
</div>
 
<script>
  // Fill dynamic fields
  const params = new URLSearchParams(window.location.search);
  document.getElementById('url-display').textContent =
    params.get('url') || document.referrer || 'The requested site';
  document.getElementById('block-reason').textContent =
    params.get('reason') || 'Unauthorized web service';
  document.getElementById('block-category').textContent =
    params.get('category') || 'Restricted Content';
  document.getElementById('time-display').textContent =
    new Date().toLocaleString();
 
  // Auto-fill site in modal
  document.getElementById('req-site').value =
    params.get('url') || '';
 
  function openRequestModal() {
    document.getElementById('requestModal').classList.add('active');
  }
  function closeRequestModal() {
    document.getElementById('requestModal').classList.remove('active');
    document.getElementById('success-msg').style.display = 'none';
  }
 
  async function submitRequest() {
    const name    = document.getElementById('req-name').value.trim();
    const empid   = document.getElementById('req-empid').value.trim();
    const dept    = document.getElementById('req-dept').value;
    const site    = document.getElementById('req-site').value.trim();
    const reason  = document.getElementById('req-reason').value.trim();
    const manager = document.getElementById('req-manager').value.trim();
 
    if (!name || !site || !reason || !manager || !dept) {
      alert('Please fill in all fields.'); return;
    }
 
    const refId = 'REQ-' + Date.now().toString(36).toUpperCase();
    const payload = { name, empid, dept, site, reason, manager,
                      refId, timestamp: new Date().toISOString(),
                      blockedUrl: params.get('url') || '' };
 
    try {
      await fetch('/api/access-request', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });
    } catch(e) { /* offline fallback — still show success */ }
 
    document.getElementById('ref-id').textContent = refId;
    document.getElementById('success-msg').style.display = 'block';
    setTimeout(closeRequestModal, 4000);
  }
</script>
</body>
</html>



Part 2: Bypass Request Workflow Backend

Save as /opt/web-blocker/request_server.py:

python

#!/usr/bin/env python3
"""
Web Access Request Workflow Backend
Handles bypass requests, manager approvals, and auto-deploys exceptions
"""
 
from http.server import HTTPServer, BaseHTTPRequestHandler
import json, smtplib, sqlite3, os, subprocess
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from datetime import datetime
from urllib.parse import urlparse, parse_qs
 
DB_FILE      = "/var/lib/web-blocker/requests.db"
SMTP_SERVER  = "smtp.company.com"
SMTP_PORT    = 587
SMTP_USER    = "it-security@company.com"
SMTP_PASS    = "your_smtp_password"
APPROVE_BASE = "https://usb-monitor.company.com/approve"
IT_EMAIL     = "it-security@company.com"
 
os.makedirs(os.path.dirname(DB_FILE), exist_ok=True)
 
# ── Database ─────────────────────────────────────────────────────────
def init_db():
    conn = sqlite3.connect(DB_FILE)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS requests (
            ref_id      TEXT PRIMARY KEY,
            name        TEXT,
            emp_id      TEXT,
            department  TEXT,
            site        TEXT,
            reason      TEXT,
            manager     TEXT,
            status      TEXT DEFAULT 'pending',
            created_at  TEXT,
            updated_at  TEXT,
            blocked_url TEXT,
            notes       TEXT
        )
    """)
    conn.commit()
    conn.close()
 
def save_request(data):
    conn = sqlite3.connect(DB_FILE)
    conn.execute("""
        INSERT OR REPLACE INTO requests
        (ref_id, name, emp_id, department, site, reason,
         manager, status, created_at, updated_at, blocked_url)
        VALUES (?,?,?,?,?,?,?,'pending',?,?,?)
    """, (
        data["refId"], data["name"], data.get("empid",""),
        data["dept"], data["site"], data["reason"],
        data["manager"], data["timestamp"], data["timestamp"],
        data.get("blockedUrl","")
    ))
    conn.commit()
    conn.close()
 
def update_request(ref_id, status, notes=""):
    conn = sqlite3.connect(DB_FILE)
    conn.execute("""
        UPDATE requests SET status=?, updated_at=?, notes=?
        WHERE ref_id=?
    """, (status, datetime.now().isoformat(), notes, ref_id))
    conn.commit()
    conn.close()
 
def get_all_requests():
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    rows = conn.execute(
        "SELECT * FROM requests ORDER BY created_at DESC"
    ).fetchall()
    conn.close()
    return [dict(r) for r in rows]
 
def get_request(ref_id):
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    row = conn.execute(
        "SELECT * FROM requests WHERE ref_id=?", (ref_id,)
    ).fetchone()
    conn.close()
    return dict(row) if row else None
 
# ── Email ────────────────────────────────────────────────────────────
def send_email(to, subject, html):
    try:
        msg = MIMEMultipart("alternative")
        msg["Subject"] = subject
        msg["From"]    = SMTP_USER
        msg["To"]      = to
        msg.attach(MIMEText(html, "html"))
        with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as s:
            s.starttls()
            s.login(SMTP_USER, SMTP_PASS)
            s.sendmail(SMTP_USER, [to], msg.as_string())
        print(f"[EMAIL] Sent to {to}: {subject}")
    except Exception as e:
        print(f"[EMAIL ERROR] {e}")
 
def notify_manager(req):
    approve_url = f"{APPROVE_BASE}?ref={req['ref_id']}&action=approve"
    deny_url    = f"{APPROVE_BASE}?ref={req['ref_id']}&action=deny"
    html = f"""
    <div style="font-family:sans-serif;max-width:600px;margin:auto">
      <div style="background:#2c3e50;color:white;padding:16px 20px;border-radius:8px 8px 0 0">
        <h2 style="margin:0">🔓 Web Access Request — Approval Required</h2>
      </div>
      <div style="background:#f9f9f9;padding:24px;border:1px solid #ddd">
        <table style="width:100%;border-collapse:collapse">
          <tr><td style="padding:8px;color:#666;width:140px">Reference</td>
              <td style="padding:8px;font-weight:bold">{req['ref_id']}</td></tr>
          <tr style="background:#fff"><td style="padding:8px;color:#666">Employee</td>
              <td style="padding:8px">{req['name']} ({req['emp_id']})</td></tr>
          <tr><td style="padding:8px;color:#666">Department</td>
              <td style="padding:8px">{req['department']}</td></tr>
          <tr style="background:#fff"><td style="padding:8px;color:#666">Site Requested</td>
              <td style="padding:8px;font-weight:bold;color:#e74c3c">{req['site']}</td></tr>
          <tr><td style="padding:8px;color:#666">Justification</td>
              <td style="padding:8px">{req['reason']}</td></tr>
          <tr style="background:#fff"><td style="padding:8px;color:#666">Submitted</td>
              <td style="padding:8px">{req['created_at'][:19]}</td></tr>
        </table>
        <div style="margin-top:24px;text-align:center">
          <a href="{approve_url}" style="background:#2ecc71;color:white;padding:12px 28px;
             border-radius:8px;text-decoration:none;font-weight:bold;margin:6px;
             display:inline-block"> Approve Access</a>
          <a href="{deny_url}" style="background:#e74c3c;color:white;padding:12px 28px;
             border-radius:8px;text-decoration:none;font-weight:bold;margin:6px;
             display:inline-block"> Deny Request</a>
        </div>
        <p style="color:#999;font-size:0.8em;margin-top:20px;text-align:center">
          IT Security | Company Name | This is an automated message
        </p>
      </div>
    </div>"""
    send_email(req["manager"],
               f"[ACTION REQUIRED] Web Access Request — {req['name']} → {req['site']}",
               html)
 
def notify_employee(req, approved):
    status_color = "#2ecc71" if approved else "#e74c3c"
    status_text  = "APPROVED ✅" if approved else "DENIED ❌"
    msg = ("Your request has been approved. Access will be granted within 30 minutes."
           if approved else
           "Your request was not approved. Contact IT if you believe this is an error.")
    html = f"""
    <div style="font-family:sans-serif;max-width:600px;margin:auto">
      <div style="background:{status_color};color:white;padding:16px 20px;
                  border-radius:8px 8px 0 0">
        <h2 style="margin:0">Web Access Request: {status_text}</h2>
      </div>
      <div style="background:#f9f9f9;padding:24px;border:1px solid #ddd">
        <p>Hi {req['name']},</p>
        <p style="margin:12px 0">{msg}</p>
        <table style="width:100%;border-collapse:collapse;margin-top:16px">
          <tr><td style="padding:8px;color:#666;width:140px">Reference</td>
              <td style="padding:8px">{req['ref_id']}</td></tr>
          <tr style="background:#fff"><td style="padding:8px;color:#666">Site</td>
              <td style="padding:8px">{req['site']}</td></tr>
          <tr><td style="padding:8px;color:#666">Status</td>
              <td style="padding:8px;font-weight:bold">{status_text}</td></tr>
        </table>
        <p style="color:#999;font-size:0.8em;margin-top:20px">
          IT Help Desk: ext. 1234 | it-security@company.com
        </p>
      </div>
    </div>"""
    # Send to employee via IT (no direct employee email in request)
    send_email(IT_EMAIL,
               f"Web Access {status_text} — {req['name']} — {req['site']}",
               html)
 
# ── Apply Exception ───────────────────────────────────────────────────
def apply_exception(site, action="allow"):
    """Add or remove a site from the block list"""
    HOSTS = "/etc/hosts"
    SQUID_WHITELIST = "/etc/squid/whitelist.txt"
 
    # Hosts file exception
    if action == "allow":
        subprocess.run(["sed", "-i",
            f"/0\\.0\\.0\\.0 {site}/d", HOSTS], check=False)
        subprocess.run(["sed", "-i",
            f"/0\\.0\\.0\\.0 www\\.{site}/d", HOSTS], check=False)
        # Add to Squid whitelist
        with open(SQUID_WHITELIST, "a") as f:
            f.write(f"{site}\n")
        subprocess.run(["squid", "-k", "reconfigure"], check=False)
        print(f"[+] Exception applied: {site} → ALLOWED")
    else:
        print(f"[+] Site remains blocked: {site}")
 
    subprocess.run(["systemd-resolve", "--flush-caches"],
                   check=False, capture_output=True)
 
# ── HTTP Request Handler ──────────────────────────────────────────────
class RequestHandler(BaseHTTPRequestHandler):
 
    def log_message(self, format, *args):
        pass  # Suppress default logs
 
    def send_json(self, code, data):
        body = json.dumps(data).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Content-Length", len(body))
        self.end_headers()
        self.wfile.write(body)
 
    def send_html(self, code, html):
        body = html.encode()
        self.send_response(code)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        self.wfile.write(body)
 
    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST,GET")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()
 
    def do_POST(self):
        if self.path == "/api/access-request":
            length = int(self.headers.get("Content-Length", 0))
            body   = json.loads(self.rfile.read(length))
            save_request(body)
            notify_manager(body)
            print(f"[REQUEST] {body['refId']} — {body['name']} → {body['site']}")
            self.send_json(200, {"status": "ok", "refId": body["refId"]})
 
    def do_GET(self):
        parsed = urlparse(self.path)
        params = parse_qs(parsed.query)
 
        # Approval endpoint
        if parsed.path == "/approve":
            ref_id = params.get("ref", [""])[0]
            action = params.get("action", [""])[0]
            req    = get_request(ref_id)
 
            if not req:
                self.send_html(404, "<h1>Request not found</h1>")
                return
 
            approved = action == "approve"
            update_request(ref_id, "approved" if approved else "denied")
 
            if approved:
                apply_exception(req["site"], "allow")
 
            notify_employee(req, approved)
            status = "APPROVED ✅" if approved else "DENIED ❌"
            color  = "#2ecc71" if approved else "#e74c3c"
            self.send_html(200, f"""
            <html><body style="font-family:sans-serif;text-align:center;
                  padding:60px;background:#0f1117;color:#e0e0e0">
              <div style="background:#1a1d27;border-radius:16px;padding:40px;
                          max-width:480px;margin:auto;border-top:4px solid {color}">
                <h1 style="color:{color}">{status}</h1>
                <p>Request <strong>{ref_id}</strong> for
                   <strong>{req['site']}</strong> has been {action}d.</p>
                <p style="color:#666;margin-top:16px">Employee will be notified.</p>
              </div>
            </body></html>""")
            return
 
        # Admin dashboard
        if parsed.path == "/admin/requests":
            requests = get_all_requests()
            rows = ""
            for r in requests:
                sc = {"approved":"#2ecc71","denied":"#e74c3c"}.get(r["status"],"#f39c12")
                rows += f"""<tr>
                  <td>{r['ref_id']}</td><td>{r['name']}</td>
                  <td>{r['department']}</td>
                  <td style='color:#e74c3c;font-weight:bold'>{r['site']}</td>
                  <td><span style='background:{sc};color:white;padding:2px 10px;
                      border-radius:10px;font-size:0.8em'>{r['status'].upper()}</span></td>
                  <td>{r['created_at'][:16]}</td>
                  <td style='font-size:0.8em;color:#888'>{r['reason'][:60]}...</td>
                </tr>"""
 
            self.send_html(200, f"""
            <!DOCTYPE html><html><head><meta charset='UTF-8'>
            <meta http-equiv='refresh' content='60'>
            <title>Access Requests</title>
            <style>
              body{{font-family:Segoe UI,sans-serif;background:#0f1117;
                   color:#e0e0e0;padding:30px}}
              h1{{color:#e74c3c}}
              table{{width:100%;border-collapse:collapse;background:#1a1d27;
                    border-radius:10px;overflow:hidden;margin-top:20px}}
              th{{background:#2c2f3e;color:#aaa;padding:11px 13px;text-align:left}}
              td{{padding:10px 13px;border-bottom:1px solid #1e2130;font-size:0.85em}}
              tr:hover td{{background:#1e2130}}
            </style></head><body>
            <h1>🔓 Web Access Requests</h1>
            <p style='color:#666'>Total: {len(requests)} | Auto-refreshes every 60s</p>
            <table>
              <tr><th>Ref ID</th><th>Name</th><th>Dept</th><th>Site</th>
                  <th>Status</th><th>Submitted</th><th>Reason</th></tr>
              {rows}
            </table></body></html>""")
            return
 
        self.send_json(404, {"error": "not found"})
 
# ── Start Server ──────────────────────────────────────────────────────
if __name__ == "__main__":
    init_db()
    print("[*] Access Request Server running on :8080")
    print(f"[*] Admin dashboard: http://localhost:8080/admin/requests")
    HTTPServer(("0.0.0.0", 8080), RequestHandler).serve_forever()

 

Part 3: Per-Department Policies

Save as /opt/web-blocker/dept_policies.py:

python

#!/usr/bin/env python3
"""
Per-Department Web Blocking Policies
Different rules for Engineering, Marketing, Finance, HR, etc.
"""
 
import json, os, subprocess
from datetime import datetime
 
POLICY_FILE  = "/opt/web-blocker/dept_policies.json"
SQUID_DIR    = "/etc/squid/departments"
HOSTS_DIR    = "/etc/hosts.d"
 
os.makedirs(SQUID_DIR, exist_ok=True)
os.makedirs(HOSTS_DIR, exist_ok=True)
 
# ── Department Policy Definitions ────────────────────────────────────
DEPT_POLICIES = {
    "engineering": {
        "description": "Developers — relaxed policy",
        "work_hours": "07:00-22:00",
        "allowed_extra": [
            "github.com", "stackoverflow.com", "gitlab.com",
            "npmjs.com", "pypi.org", "docker.com",
            "aws.amazon.com", "cloud.google.com"
        ],
        "blocked_extra": [],
        "social_media":    False,   # Blocked
        "personal_email":  False,   # Blocked
        "youtube":         True,    # Allowed (tutorials)
        "file_sharing":    False,   # Blocked
        "streaming":       False,   # Blocked
    },
 
    "marketing": {
        "description": "Marketing — social media allowed",
        "work_hours": "08:00-19:00",
        "allowed_extra": [
            "canva.com", "figma.com", "hootsuite.com",
            "buffer.com", "mailchimp.com", "hubspot.com"
        ],
        "blocked_extra": ["tiktok.com"],
        "social_media":    True,    # ALLOWED for work
        "personal_email":  False,
        "youtube":         True,    # ALLOWED
        "file_sharing":    False,
        "streaming":       False,
    },
 
    "finance": {
        "description": "Finance — strict policy",
        "work_hours": "08:00-18:00",
        "allowed_extra": [
            "bloomberg.com", "reuters.com",
            "moneycontrol.com", "nseindia.com"
        ],
        "blocked_extra": [
            "reddit.com", "quora.com", "medium.com"
        ],
        "social_media":    False,
        "personal_email":  False,
        "youtube":         False,
        "file_sharing":    False,
        "streaming":       False,
    },
 
    "hr": {
        "description": "HR — LinkedIn and job sites allowed",
        "work_hours": "08:00-18:00",
        "allowed_extra": [
            "linkedin.com", "naukri.com", "indeed.com",
            "glassdoor.com", "shine.com", "monster.com"
        ],
        "blocked_extra": [],
        "social_media":    False,
        "personal_email":  False,
        "youtube":         True,
        "file_sharing":    False,
        "streaming":       False,
    },
 
    "executive": {
        "description": "Leadership — minimal restrictions",
        "work_hours": "06:00-23:00",
        "allowed_extra": ["*"],     # Wildcard — allow all extras
        "blocked_extra": [],
        "social_media":    True,
        "personal_email":  True,    # ALLOWED
        "youtube":         True,
        "file_sharing":    True,
        "streaming":       False,
    },
 
    "default": {
        "description": "Standard employee policy",
        "work_hours": "08:00-18:00",
        "allowed_extra": [],
        "blocked_extra": [],
        "social_media":    False,
        "personal_email":  False,
        "youtube":         False,
        "file_sharing":    False,
        "streaming":       False,
    }
}
 
# ── Base block lists ──────────────────────────────────────────────────
BASE_BLOCKS = {
    "social_media":   ["facebook.com","twitter.com","x.com",
                       "instagram.com","tiktok.com","snapchat.com","reddit.com"],
    "personal_email": ["gmail.com","mail.google.com","hotmail.com",
                       "yahoo.com","protonmail.com","tutanota.com"],
    "youtube":        ["youtube.com","youtu.be"],
    "file_sharing":   ["dropbox.com","wetransfer.com","mega.nz",
                       "mediafire.com","pastebin.com"],
    "streaming":      ["netflix.com","primevideo.com","twitch.tv","spotify.com"],
}
 
# ── Generate Squid ACL per department ────────────────────────────────
def generate_squid_acl(dept, policy):
    blocked = []
    for category, sites in BASE_BLOCKS.items():
        if not policy.get(category, True):   # False = blocked
            blocked.extend(sites)
    blocked.extend(policy.get("blocked_extra", []))
 
    allowed = policy.get("allowed_extra", [])
    wh      = policy.get("work_hours", "08:00-18:00")
    wh_start, wh_end = wh.split("-")
 
    blocked_file = f"{SQUID_DIR}/{dept}_blocked.txt"
    allowed_file = f"{SQUID_DIR}/{dept}_allowed.txt"
 
    with open(blocked_file, "w") as f:
        f.write("\n".join(set(blocked)))
    with open(allowed_file, "w") as f:
        f.write("\n".join(set(allowed)))
 
    acl = f"""
# ── Department: {dept.upper()} ── {policy['description']} ──
acl dept_{dept}_net  src "/etc/squid/departments/{dept}_ips.txt"
acl dept_{dept}_blk  dstdomain "{blocked_file}"
acl dept_{dept}_wl   dstdomain "{allowed_file}"
acl dept_{dept}_hrs  time MTWHF {wh_start}-{wh_end}
 
# Whitelist takes priority
http_access allow dept_{dept}_net dept_{dept}_wl
# Block during work hours
http_access deny  dept_{dept}_net dept_{dept}_blk dept_{dept}_hrs
"""
    return acl
 
# ── Generate Hosts file per department ───────────────────────────────
def generate_hosts_block(dept, policy):
    blocked = []
    for category, sites in BASE_BLOCKS.items():
        if not policy.get(category, True):
            blocked.extend(sites)
    blocked.extend(policy.get("blocked_extra", []))
    allowed = policy.get("allowed_extra", [])
 
    lines = [f"# DEPT-BLOCK-{dept.upper()}-START"]
    for site in set(blocked):
        if site not in allowed:
            lines.append(f"0.0.0.0 {site}     # DEPT-BLOCK-{dept.upper()}")
            lines.append(f"0.0.0.0 www.{site} # DEPT-BLOCK-{dept.upper()}")
    lines.append(f"# DEPT-BLOCK-{dept.upper()}-END")
    return "\n".join(lines)
 
# ── Deploy all department policies ───────────────────────────────────
def deploy_all_policies():
    all_acls  = ["# Auto-generated department ACLs\n"]
    all_hosts = {}
 
    for dept, policy in DEPT_POLICIES.items():
        acl   = generate_squid_acl(dept, policy)
        hosts = generate_hosts_block(dept, policy)
        all_acls.append(acl)
        all_hosts[dept] = hosts
 
        # Write per-dept hosts file
        with open(f"{HOSTS_DIR}/{dept}.conf", "w") as f:
            f.write(hosts)
 
        print(f"[+] Policy deployed: {dept:15} — {policy['description']}")
 
    # Write combined Squid ACL
    with open(f"{SQUID_DIR}/dept_acls.conf", "w") as f:
        f.write("\n".join(all_acls))
 
    # Reconfigure Squid
    result = subprocess.run(["squid", "-k", "reconfigure"],
                            capture_output=True, text=True)
    if result.returncode == 0:
        print("[✔] Squid reconfigured")
    else:
        print(f"[!] Squid error: {result.stderr}")
 
    # Save policy DB
    with open(POLICY_FILE, "w") as f:
        json.dump(DEPT_POLICIES, f, indent=2)
 
    print(f"\n[✔] All department policies deployed: {datetime.now()}")
 
# ── Policy Summary ────────────────────────────────────────────────────
def print_summary():
    print("\n" + "="*70)
    print(f"{'DEPARTMENT':<15} {'SOCIAL':^8} {'EMAIL':^8} "
          f"{'YOUTUBE':^9} {'FILES':^8} {'WORK HOURS'}")
    print("="*70)
    icons = {True: "✅", False: "❌"}
    for dept, p in DEPT_POLICIES.items():
        print(f"{dept:<15} "
              f"{icons[p['social_media']]:^8} "
              f"{icons[p['personal_email']]:^8} "
              f"{icons[p['youtube']]:^9} "
              f"{icons[p['file_sharing']]:^8} "
              f"{p['work_hours']}")
    print("="*70)
 
if __name__ == "__main__":
    deploy_all_policies()
    print_summary()

Squid Integration — Add to squid.conf

squid

# Include department ACLs
include /etc/squid/departments/dept_acls.conf
 
# Include whitelist for approved exceptions
acl approved_exceptions dstdomain "/etc/squid/whitelist.txt"
http_access allow approved_exceptions
 
# Block page
deny_info https://usb-monitor.company.com/blocked/index.html?url=%u&category=%s all

Deploy Everything

bash

# 1. Deploy block page
sudo mkdir -p /var/www/html/blocked
sudo cp index.html /var/www/html/blocked/
 
# 2. Start request server
sudo python3 /opt/web-blocker/request_server.py &
# Or as a service:
sudo tee /etc/systemd/system/web-request-server.service <<EOF
[Unit]
Description=Web Access Request Server
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/web-blocker/request_server.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now web-request-server
 
# 3. Deploy department policies
sudo python3 /opt/web-blocker/dept_policies.py
 
# 4. Verify
sudo squid -k parse && sudo squid -k reconfigure

Complete Workflow

Employee tries gmail.com
        ↓
  Squid/DNS blocks it
        ↓
  Branded block page shown
        ↓
  Employee clicks "Request Access"
        ↓
  Fills form → submits
        ↓
  Manager gets email with [Approve] [Deny] buttons
        ↓
    ┌───┴───┐
 Approve   Deny
    ↓        ↓
Site added  Request
to whitelist denied
    ↓        ↓
Squid      Employee
reconfigured notified
    ↓
Access granted
within 30 min

Department Policy Matrix

Department

Social Media

Personal Email

YouTube

File Sharing

Work Hours

Engineering

7AM–10PM

Marketing

8AM–7PM

Finance

8AM–6PM

HR

8AM–6PM

Executive

6AM–11PM

Default

8AM–6PM

 

 

 

 

Here's the complete implementation for both AD/LDAP integration and the reporting dashboard.


Part 1: Active Directory / LDAP Integration

Windows AD — Squid Kerberos Authentication

Step 1: Join Squid server to AD domain

bash

# Install required packages
sudo apt install -y krb5-user samba winbind libpam-winbind \
    libnss-winbind krb5-config msktutil
 
# Configure Kerberos
sudo tee /etc/krb5.conf <<EOF
[libdefaults]
    default_realm = COMPANY.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true
    ticket_lifetime = 24h
    forwardable = true
 
[realms]
    COMPANY.COM = {
        kdc = dc01.company.com
        admin_server = dc01.company.com
    }
 
[domain_realm]
    .company.com = COMPANY.COM
    company.com  = COMPANY.COM
EOF
 
# Configure Samba/Winbind
sudo tee /etc/samba/smb.conf <<EOF
[global]
    workgroup        = COMPANY
    realm            = COMPANY.COM
    security         = ADS
    kerberos method  = secrets and keytab
    winbind use default domain = yes
    winbind enum users  = yes
    winbind enum groups = yes
    idmap config * : backend = tdb
    idmap config * : range   = 10000-99999
EOF
 
# Join domain
sudo net ads join -U Administrator
 
# Start winbind
sudo systemctl enable --now winbind
 
# Test
wbinfo -u   # List AD users
wbinfo -g   # List AD groups

Step 2: Create Squid AD service account

powershell

# Run on Windows DC — PowerShell
# Create dedicated service account for Squid
New-ADUser `
    -Name            "squid-proxy" `
    -SamAccountName  "squid-proxy" `
    -UserPrincipalName "squid-proxy@company.com" `
    -AccountPassword (ConvertTo-SecureString "Str0ngP@ss!" -AsPlainText -Force) `
    -PasswordNeverExpires $true `
    -CannotChangePassword $true `
    -Enabled $true `
    -Description "Squid Proxy Service Account"
 
# Create SPN for Kerberos
setspn -A HTTP/proxy.company.com squid-proxy
setspn -A HTTP/proxy         squid-proxy
 
# Verify SPNs
setspn -L squid-proxy

Step 3: Generate Kerberos keytab

bash

# On Squid server
sudo msktutil -c -b "CN=Computers" \
    -s HTTP/proxy.company.com \
    -k /etc/squid/squid.keytab \
    --computer-name squid-proxy \
    --upn squid-proxy \
    --server dc01.company.com \
    --enctypes 28
 
sudo chown proxy:proxy /etc/squid/squid.keytab
sudo chmod 600 /etc/squid/squid.keytab
 
# Test keytab
kinit -k -t /etc/squid/squid.keytab HTTP/proxy.company.com
klist

Step 4: LDAP Group Sync Script

Save as /opt/web-blocker/ad_ldap_sync.py:

python

#!/usr/bin/env python3
"""
AD / LDAP Group → Department Policy Sync
Automatically assigns web policies based on AD group membership
"""
 
import ldap3, json, os, subprocess, sqlite3
from datetime import datetime
 
# ── Config ────────────────────────────────────────────────────────────
AD_CONFIG = {
    "server":   "ldap://dc01.company.com",
    "domain":   "company.com",
    "base_dn":  "DC=company,DC=com",
    "bind_dn":  "CN=squid-proxy,CN=Users,DC=company,DC=com",
    "bind_pw":  "Str0ngP@ss!",
    "user_ou":  "OU=Users,DC=company,DC=com",
    "group_ou": "OU=Groups,DC=company,DC=com",
}
 
OPENLDAP_CONFIG = {
    "server":   "ldap://ldap.company.com",
    "base_dn":  "dc=company,dc=com",
    "bind_dn":  "cn=admin,dc=company,dc=com",
    "bind_pw":  "ldap_admin_pass",
    "user_ou":  "ou=users,dc=company,dc=com",
    "group_ou": "ou=groups,dc=company,dc=com",
}
 
# AD Group → Department mapping
GROUP_DEPT_MAP = {
    "CN=Engineering,OU=Groups,DC=company,DC=com":  "engineering",
    "CN=Marketing,OU=Groups,DC=company,DC=com":    "marketing",
    "CN=Finance,OU=Groups,DC=company,DC=com":      "finance",
    "CN=HR,OU=Groups,DC=company,DC=com":           "hr",
    "CN=Executives,OU=Groups,DC=company,DC=com":   "executive",
    "CN=IT,OU=Groups,DC=company,DC=com":           "engineering",
    "CN=Sales,OU=Groups,DC=company,DC=com":        "marketing",
}
 
DB_FILE    = "/var/lib/web-blocker/users.db"
SQUID_DIR  = "/etc/squid/departments"
POLICY_LOG = "/var/log/web-blocker/ldap-sync.log"
 
os.makedirs(os.path.dirname(DB_FILE),    exist_ok=True)
os.makedirs(os.path.dirname(POLICY_LOG), exist_ok=True)
 
# ── Database ──────────────────────────────────────────────────────────
def init_db():
    conn = sqlite3.connect(DB_FILE)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS user_policies (
            username    TEXT PRIMARY KEY,
            full_name   TEXT,
            email       TEXT,
            department  TEXT,
            policy      TEXT,
            groups      TEXT,
            ip_address  TEXT,
            last_sync   TEXT,
            source      TEXT
        )
    """)
    conn.commit()
    conn.close()
 
def save_user(username, full_name, email, dept, groups, source="AD"):
    conn = sqlite3.connect(DB_FILE)
    conn.execute("""
        INSERT OR REPLACE INTO user_policies
        (username, full_name, email, department, policy, groups, last_sync, source)
        VALUES (?,?,?,?,?,?,?,?)
    """, (username, full_name, email, dept, dept,
          json.dumps(groups), datetime.now().isoformat(), source))
    conn.commit()
    conn.close()
 
def get_all_users():
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    rows = conn.execute(
        "SELECT * FROM user_policies ORDER BY department, username"
    ).fetchall()
    conn.close()
    return [dict(r) for r in rows]
 
def log(msg):
    ts   = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line)
    with open(POLICY_LOG, "a") as f:
        f.write(line + "\n")
 
# ── LDAP Connection ───────────────────────────────────────────────────
def connect_ldap(config, use_ssl=False):
    server = ldap3.Server(
        config["server"],
        get_info=ldap3.ALL,
        use_ssl=use_ssl
    )
    conn = ldap3.Connection(
        server,
        user=config["bind_dn"],
        password=config["bind_pw"],
        authentication=ldap3.SIMPLE,
        auto_bind=True
    )
    return conn
 
# ── Fetch Users from AD ───────────────────────────────────────────────
def fetch_ad_users(conn, config):
    users = {}
    conn.search(
        search_base=config["user_ou"],
        search_filter="(&(objectClass=user)(objectCategory=person)"
                      "(!(userAccountControl:1.2.840.113556.1.4.803:=2)))",
        search_scope=ldap3.SUBTREE,
        attributes=[
            "sAMAccountName", "displayName", "mail",
            "memberOf", "department", "distinguishedName",
            "lastLogon", "userAccountControl"
        ]
    )
    for entry in conn.entries:
        username  = str(entry.sAMAccountName)
        full_name = str(entry.displayName)   if entry.displayName else username
        email     = str(entry.mail)          if entry.mail        else ""
        groups    = [str(g) for g in entry.memberOf] if entry.memberOf else []
        ad_dept   = str(entry.department)    if entry.department  else ""
 
        # Determine policy department
        dept = "default"
        for group_dn, group_dept in GROUP_DEPT_MAP.items():
            if group_dn in groups:
                dept = group_dept
                break
        if ad_dept and dept == "default":
            dept = ad_dept.lower().replace(" ", "_")
 
        users[username] = {
            "full_name": full_name,
            "email":     email,
            "dept":      dept,
            "groups":    groups,
            "source":    "ActiveDirectory"
        }
    log(f"[AD] Fetched {len(users)} users")
    return users
 
# ── Fetch Users from OpenLDAP ─────────────────────────────────────────
def fetch_ldap_users(conn, config):
    users = {}
 
    # Fetch groups first
    group_members = {}
    conn.search(
        search_base=config["group_ou"],
        search_filter="(objectClass=groupOfNames)",
        search_scope=ldap3.SUBTREE,
        attributes=["cn", "member"]
    )
    for entry in conn.entries:
        group_name = str(entry.cn).lower()
        members    = [str(m) for m in entry.member] if entry.member else []
        group_members[group_name] = members
 
    # Fetch users
    conn.search(
        search_base=config["user_ou"],
        search_filter="(objectClass=inetOrgPerson)",
        search_scope=ldap3.SUBTREE,
        attributes=["uid", "cn", "mail", "departmentNumber", "ou"]
    )
    for entry in conn.entries:
        username  = str(entry.uid)
        full_name = str(entry.cn)   if entry.cn   else username
        email     = str(entry.mail) if entry.mail else ""
        user_dn   = str(entry.entry_dn)
 
        # Find which groups this user belongs to
        user_groups = [g for g, members in group_members.items()
                       if user_dn in members]
 
        # Map group → department
        dept = "default"
        for group in user_groups:
            for key, val in {
                "engineering": "engineering",
                "developers":  "engineering",
                "marketing":   "marketing",
                "finance":     "finance",
                "hr":          "hr",
                "executive":   "executive",
                "management":  "executive",
            }.items():
                if key in group:
                    dept = val
                    break
 
        users[username] = {
            "full_name": full_name,
            "email":     email,
            "dept":      dept,
            "groups":    user_groups,
            "source":    "OpenLDAP"
        }
    log(f"[LDAP] Fetched {len(users)} users")
    return users
 
# ── Generate Squid per-user ACLs ──────────────────────────────────────
def generate_squid_user_acls(all_users):
    dept_users = {}
    for username, data in all_users.items():
        dept = data["dept"]
        dept_users.setdefault(dept, []).append(username)
 
    acl_lines = ["# Auto-generated user→department ACLs",
                 f"# Last sync: {datetime.now()}",
                 ""]
 
    for dept, users in dept_users.items():
        user_list = " ".join(users)
        acl_lines.append(f"# {dept.upper()} — {len(users)} users")
        acl_lines.append(f"acl dept_{dept}_users proxy_auth {user_list}")
        acl_lines.append(f"http_access allow dept_{dept}_users dept_{dept}_wl")
        acl_lines.append(f"http_access deny  dept_{dept}_users dept_{dept}_blk")
        acl_lines.append("")
 
    acl_file = f"{SQUID_DIR}/user_acls.conf"
    with open(acl_file, "w") as f:
        f.write("\n".join(acl_lines))
 
    log(f"[SQUID] User ACLs written: {acl_file}")
 
    # Reconfigure Squid
    result = subprocess.run(["squid", "-k", "reconfigure"],
                            capture_output=True, text=True)
    if result.returncode == 0:
        log("[SQUID] Reconfigured successfully")
    else:
        log(f"[SQUID ERROR] {result.stderr.strip()}")
 
# ── Main Sync ─────────────────────────────────────────────────────────
def sync_all():
    init_db()
    all_users = {}
 
    # Sync from Active Directory
    try:
        log("[*] Connecting to Active Directory...")
        ad_conn   = connect_ldap(AD_CONFIG)
        ad_users  = fetch_ad_users(ad_conn, AD_CONFIG)
        all_users.update(ad_users)
        for u, d in ad_users.items():
            save_user(u, d["full_name"], d["email"],
                      d["dept"], d["groups"], "AD")
        ad_conn.unbind()
    except Exception as e:
        log(f"[AD ERROR] {e}")
 
    # Sync from OpenLDAP
    try:
        log("[*] Connecting to OpenLDAP...")
        ldap_conn  = connect_ldap(OPENLDAP_CONFIG)
        ldap_users = fetch_ldap_users(ldap_conn, OPENLDAP_CONFIG)
        all_users.update(ldap_users)
        for u, d in ldap_users.items():
            save_user(u, d["full_name"], d["email"],
                      d["dept"], d["groups"], "OpenLDAP")
        ldap_conn.unbind()
    except Exception as e:
        log(f"[LDAP ERROR] {e}")
 
    # Generate Squid ACLs
    generate_squid_user_acls(all_users)
 
    log(f"[✔] Sync complete — {len(all_users)} users across "
        f"{len(set(d['dept'] for d in all_users.values()))} departments")
    return all_users
 
if __name__ == "__main__":
    sync_all()

Squid — Enable LDAP/Kerberos Auth

Add to /etc/squid/squid.conf:

squid

# ── Kerberos / NTLM Authentication (AD) ─────────────────────────────
auth_param negotiate program /usr/lib/squid/negotiate_kerberos_auth \
    -s HTTP/proxy.company.com@COMPANY.COM \
    -k /etc/squid/squid.keytab \
    -t none
auth_param negotiate children 20 startup=5 idle=1
auth_param negotiate keep_alive on
 
# ── NTLM fallback ────────────────────────────────────────────────────
auth_param ntlm program /usr/bin/ntlm_auth \
    --helper-protocol=squid-2.5-ntlmssp
auth_param ntlm children 20
 
# ── Basic LDAP fallback (OpenLDAP) ───────────────────────────────────
auth_param basic program /usr/lib/squid/basic_ldap_auth \
    -R -b "dc=company,dc=com" \
    -D "cn=squid-proxy,cn=Users,dc=company,dc=com" \
    -w "Str0ngP@ss!" \
    -f "uid=%s" \
    -h ldap.company.com
auth_param basic realm "Company Internet Proxy"
auth_param basic children 10
 
# Require authentication
acl authenticated proxy_auth REQUIRED
http_access deny !authenticated
 
# Include user ACLs (auto-generated by sync script)
include /etc/squid/departments/user_acls.conf
include /etc/squid/departments/dept_acls.conf

Part 2: Reporting Dashboard

Save as /opt/web-blocker/report_dashboard.py:

python

#!/usr/bin/env python3
"""
Web Blocking Reports Dashboard
Parses Squid access logs → rich analytics dashboard
"""
 
import re, json, os, sqlite3
from datetime import datetime, timedelta
from collections import defaultdict, Counter
 
SQUID_LOG   = "/var/log/squid/access.log"
REPORT_HTML = "/var/www/html/usb-dashboard/webreport.html"
DB_FILE     = "/var/lib/web-blocker/users.db"
 
# ── Parse Squid Log ───────────────────────────────────────────────────
def parse_squid_log(hours=24):
    """
    Squid log format:
    timestamp elapsed client action/code bytes method url user ...
    """
    cutoff  = datetime.now() - timedelta(hours=hours)
    events  = []
    pattern = re.compile(
        r'(\d+\.\d+)\s+\d+\s+(\S+)\s+(\w+)/(\d+)\s+\d+\s+(\w+)\s+(\S+)'
        r'\s+(\S+)'
    )
 
    try:
        with open(SQUID_LOG, "r", errors="ignore") as f:
            for line in f:
                m = pattern.match(line)
                if not m:
                    continue
                ts, client, action, code, method, url, user = m.groups()
                event_time = datetime.fromtimestamp(float(ts))
                if event_time < cutoff:
                    continue
                events.append({
                    "ts":      event_time,
                    "client":  client,
                    "action":  action,
                    "code":    int(code),
                    "method":  method,
                    "url":     url,
                    "user":    user.replace("-", "anonymous"),
                    "blocked": action in ("TCP_DENIED","TCP_MISS_ABORTED")
                             or int(code) in (403, 407)
                })
    except FileNotFoundError:
        print(f"[!] Log not found: {SQUID_LOG}")
 
    return events
 
# ── Analyse Events ────────────────────────────────────────────────────
def analyse(events):
    total       = len(events)
    blocked     = [e for e in events if e["blocked"]]
    allowed     = [e for e in events if not e["blocked"]]
 
    # Extract domain from URL
    def domain(url):
        m = re.search(r'https?://([^/:]+)', url)
        if m: return m.group(1).lstrip("www.")
        m = re.search(r'([^/:]+):\d+', url)
        if m: return m.group(1).lstrip("www.")
        return url[:50]
 
    # Top blocked domains
    blocked_domains = Counter(domain(e["url"]) for e in blocked)
    top_blocked     = blocked_domains.most_common(15)
 
    # Top users attempting blocked sites
    blocked_users   = Counter(e["user"] for e in blocked)
    top_users       = blocked_users.most_common(10)
 
    # Blocked attempts by hour
    by_hour = defaultdict(int)
    for e in blocked:
        by_hour[e["ts"].strftime("%H:00")] += 1
    hours_sorted = sorted(by_hour.items())
 
    # Blocked by category (simple keyword match)
    categories = {
        "Personal Email":  ["gmail","hotmail","yahoo","protonmail","tutanota","outlook.live"],
        "Social Media":    ["facebook","twitter","instagram","tiktok","snapchat","reddit","x.com"],
        "Streaming":       ["youtube","netflix","twitch","spotify","primevideo"],
        "File Sharing":    ["dropbox","wetransfer","mega.nz","mediafire","pastebin"],
        "Gaming":          ["steam","epicgames","roblox","battlenet"],
    }
    cat_counts = defaultdict(int)
    for e in blocked:
        d = domain(e["url"])
        matched = False
        for cat, keywords in categories.items():
            if any(k in d for k in keywords):
                cat_counts[cat] += 1
                matched = True
                break
        if not matched:
            cat_counts["Other"] += 1
 
    # Client IP activity
    top_clients = Counter(e["client"] for e in blocked).most_common(10)
 
    # Hourly trend (all traffic)
    hourly_all = defaultdict(int)
    hourly_blk = defaultdict(int)
    for e in events:
        h = e["ts"].strftime("%H:00")
        hourly_all[h] += 1
        if e["blocked"]:
            hourly_blk[h] += 1
    all_hours    = sorted(set(hourly_all) | set(hourly_blk))
    hourly_total = [hourly_all.get(h, 0) for h in all_hours]
    hourly_block = [hourly_blk.get(h, 0) for h in all_hours]
 
    return {
        "total":          total,
        "blocked_count":  len(blocked),
        "allowed_count":  len(allowed),
        "block_rate":     round(len(blocked) / total * 100, 1) if total else 0,
        "top_blocked":    top_blocked,
        "top_users":      top_users,
        "cat_counts":     dict(cat_counts),
        "top_clients":    top_clients,
        "all_hours":      all_hours,
        "hourly_total":   hourly_total,
        "hourly_block":   hourly_block,
    }
 
# ── Get user department from DB ───────────────────────────────────────
def get_user_dept_map():
    try:
        conn = sqlite3.connect(DB_FILE)
        rows = conn.execute(
            "SELECT username, department FROM user_policies"
        ).fetchall()
        conn.close()
        return {r[0]: r[1] for r in rows}
    except:
        return {}
 
# ── Generate Dashboard HTML ───────────────────────────────────────────
def generate_dashboard(stats, period_hours=24):
    top_blocked_rows = "".join(f"""
        <tr>
          <td>{i+1}</td>
          <td style='color:#e74c3c'>{domain}</td>
          <td>{count}</td>
          <td>
            <div style='background:#2c2f3e;border-radius:4px;height:8px;width:200px'>
              <div style='background:#e74c3c;width:{min(count/max(stats["top_blocked"][0][1],1)*100,100):.0f}%;
                   height:8px;border-radius:4px'></div>
            </div>
          </td>
        </tr>"""
        for i, (domain, count) in enumerate(stats["top_blocked"]))
 
    top_users_rows = "".join(f"""
        <tr>
          <td>👤 {user}</td>
          <td>{count}</td>
          <td>
            <div style='background:#2c2f3e;border-radius:4px;height:8px;width:150px'>
              <div style='background:#f39c12;
                   width:{min(count/max(stats["top_users"][0][1],1)*100,100):.0f}%;
                   height:8px;border-radius:4px'></div>
            </div>
          </td>
        </tr>"""
        for user, count in stats["top_users"])
 
    cat_labels = json.dumps(list(stats["cat_counts"].keys()))
    cat_values = json.dumps(list(stats["cat_counts"].values()))
    hour_labels = json.dumps(stats["all_hours"])
    hour_total  = json.dumps(stats["hourly_total"])
    hour_block  = json.dumps(stats["hourly_block"])
 
    html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="300">
<title>Web Blocking Report</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
  * {{ box-sizing:border-box; margin:0; padding:0 }}
  body {{ font-family:Segoe UI,Ubuntu,sans-serif;
         background:#0f1117; color:#e0e0e0; }}
  header {{ background:#1a1d27; padding:18px 30px;
            border-bottom:2px solid #e74c3c;
            display:flex; justify-content:space-between; align-items:center }}
  header h1 {{ color:#e74c3c; font-size:1.4em }}
  header span {{ color:#555; font-size:0.82em }}
  nav {{ background:#1a1d27; padding:0 30px;
         border-bottom:1px solid #2c2f3e;
         display:flex; gap:4px }}
  nav a {{ color:#888; padding:12px 18px; text-decoration:none;
           font-size:0.88em; border-bottom:2px solid transparent;
           transition:all 0.2s }}
  nav a:hover, nav a.active {{ color:#e74c3c;
                               border-bottom-color:#e74c3c }}
  .main {{ padding:24px 30px }}
  .summary {{ display:grid;
              grid-template-columns:repeat(4,1fr);
              gap:16px; margin-bottom:24px }}
  .card {{ background:#1a1d27; border-radius:10px;
           padding:18px 22px;
           border-left:4px solid #e74c3c }}
  .card h3 {{ font-size:2em; color:#e74c3c; margin:0 }}
  .card p  {{ color:#888; font-size:0.82em; margin-top:5px }}
  .card.green  {{ border-color:#2ecc71 }}
  .card.green h3 {{ color:#2ecc71 }}
  .card.orange {{ border-color:#f39c12 }}
  .card.orange h3 {{ color:#f39c12 }}
  .card.blue   {{ border-color:#3498db }}
  .card.blue h3 {{ color:#3498db }}
  .grid2 {{ display:grid; grid-template-columns:1fr 1fr;
            gap:16px; margin-bottom:20px }}
  .grid3 {{ display:grid; grid-template-columns:1fr 1fr 1fr;
            gap:16px; margin-bottom:20px }}
  .panel {{ background:#1a1d27; border-radius:10px; padding:20px }}
  .panel h2 {{ font-size:0.95em; color:#e74c3c;
               margin-bottom:16px;
               padding-bottom:8px;
               border-bottom:1px solid #2c2f3e }}
  table {{ width:100%; border-collapse:collapse;
           font-size:0.84em }}
  th {{ background:#2c2f3e; color:#aaa;
        padding:10px 12px; text-align:left;
        font-weight:500 }}
  td {{ padding:9px 12px;
        border-bottom:1px solid #1e2130 }}
  tr:hover td {{ background:#1e2130 }}
  input[type=text] {{ background:#2c2f3e; border:1px solid #444;
                      color:#eee; padding:7px 12px;
                      border-radius:6px; width:260px;
                      margin-bottom:12px; font-family:inherit }}
  .badge {{ padding:2px 9px; border-radius:10px;
            font-size:0.78em; font-weight:bold }}
  @media(max-width:900px) {{
    .summary,.grid2,.grid3 {{ grid-template-columns:1fr 1fr }}
  }}
</style>
</head>
<body>
 
<header>
  <h1>🌐 Web Blocking Report Dashboard</h1>
  <span>Last {period_hours}h &nbsp;|&nbsp;
        Auto-refresh 5 min &nbsp;|&nbsp;
        {datetime.now().strftime('%Y-%m-%d %H:%M')}</span>
</header>
 
<nav>
  <a href="index.html">🔌 USB Audit</a>
  <a href="risk.html">👤 Risk Scores</a>
  <a href="webreport.html" class="active">🌐 Web Blocking</a>
  <a href="/admin/requests">🔓 Access Requests</a>
  <a href="webblocker.html"> Policy Status</a>
</nav>
 
<div class="main">
 
  <!-- Summary Cards -->
  <div class="summary">
    <div class="card">
      <h3>{stats['total']:,}</h3>
      <p>Total Requests</p>
    </div>
    <div class="card">
      <h3>{stats['blocked_count']:,}</h3>
      <p>Blocked Requests</p>
    </div>
    <div class="card green">
      <h3>{stats['allowed_count']:,}</h3>
      <p>Allowed Requests</p>
    </div>
    <div class="card orange">
      <h3>{stats['block_rate']}%</h3>
      <p>Block Rate</p>
    </div>
  </div>
 
  <!-- Traffic Chart (full width) -->
  <div class="panel" style="margin-bottom:20px">
    <h2>📈 Hourly Traffic — Allowed vs Blocked</h2>
    <canvas id="hourlyChart" height="80"></canvas>
  </div>
 
  <!-- Category + Top Domains -->
  <div class="grid2">
    <div class="panel">
      <h2>📊 Blocks by Category</h2>
      <canvas id="catChart" height="220"></canvas>
    </div>
    <div class="panel">
      <h2>🚫 Top Blocked Domains</h2>
      <input type="text" id="domainSearch"
             placeholder="🔍 Filter domains..."
             onkeyup="filterTable('domainTable','domainSearch')">
      <table id="domainTable">
        <tr><th>#</th><th>Domain</th><th>Attempts</th><th>Volume</th></tr>
        {top_blocked_rows}
      </table>
    </div>
  </div>
 
  <!-- Top Users -->
  <div class="grid2">
    <div class="panel">
      <h2>👤 Top Users — Blocked Attempts</h2>
      <table>
        <tr><th>User</th><th>Attempts</th><th>Volume</th></tr>
        {top_users_rows}
      </table>
    </div>
    <div class="panel">
      <h2>🖥 Top Client IPs — Blocked</h2>
      <table>
        <tr><th>IP Address</th><th>Attempts</th></tr>
        {"".join(f"<tr><td>{ip}</td><td>{n}</td></tr>"
                 for ip,n in stats['top_clients'])}
      </table>
    </div>
  </div>
 
</div>
 
<script>
// Hourly chart
new Chart(document.getElementById('hourlyChart'), {{
  type: 'line',
  data: {{
    labels: {hour_labels},
    datasets: [
      {{
        label: 'Allowed',
        data:  {hour_total},
        borderColor: '#2ecc71',
        backgroundColor: 'rgba(46,204,113,0.1)',
        fill: true, tension: 0.4, pointRadius: 3
      }},
      {{
        label: 'Blocked',
        data:  {hour_block},
        borderColor: '#e74c3c',
        backgroundColor: 'rgba(231,76,60,0.15)',
        fill: true, tension: 0.4, pointRadius: 3
      }}
    ]
  }},
  options: {{
    responsive: true,
    plugins: {{ legend: {{ labels: {{ color:'#aaa' }} }} }},
    scales: {{
      x: {{ ticks:{{ color:'#666' }}, grid:{{ color:'#1e2130' }} }},
      y: {{ ticks:{{ color:'#666' }}, grid:{{ color:'#1e2130' }} }}
    }}
  }}
}});
 
// Category donut
new Chart(document.getElementById('catChart'), {{
  type: 'doughnut',
  data: {{
    labels: {cat_labels},
    datasets: [{{
      data: {cat_values},
      backgroundColor: [
        '#e74c3c','#f39c12','#3498db',
        '#2ecc71','#9b59b6','#1abc9c'
      ]
    }}]
  }},
  options: {{
    responsive: true,
    plugins: {{ legend: {{ labels: {{ color:'#aaa' }} }} }}
  }}
}});
 
// Table filter
function filterTable(tableId, inputId) {{
  const q = document.getElementById(inputId).value.toLowerCase();
  document.querySelectorAll(`#${{tableId}} tr:not(:first-child)`).forEach(r => {{
    r.style.display = r.innerText.toLowerCase().includes(q) ? '' : 'none';
  }});
}}
</script>
</body>
</html>"""
 
    os.makedirs(os.path.dirname(REPORT_HTML), exist_ok=True)
    with open(REPORT_HTML, "w") as f:
        f.write(html)
    print(f"[✔] Dashboard: {REPORT_HTML}")
    print(f"    Total: {stats['total']:,}  Blocked: {stats['blocked_count']:,}"
          f"  Rate: {stats['block_rate']}%")
 
if __name__ == "__main__":
    print("[*] Parsing Squid logs...")
    events = parse_squid_log(hours=24)
    print(f"[*] Analysing {len(events):,} events...")
    stats  = analyse(events)
    generate_dashboard(stats)

Deploy & Schedule Everything

bash

# Install Python LDAP library
pip3 install ldap3 --break-system-packages
 
# Test AD/LDAP sync
sudo python3 /opt/web-blocker/ad_ldap_sync.py
 
# Test report generation
sudo python3 /opt/web-blocker/report_dashboard.py
 
# Deploy as services
sudo tee /etc/systemd/system/ldap-sync.service <<EOF
[Unit]
Description=AD/LDAP Policy Sync
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/web-blocker/ad_ldap_sync.py
Type=oneshot
EOF
 
sudo tee /etc/systemd/system/ldap-sync.timer <<EOF
[Unit]
Description=Run AD/LDAP sync every 15 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target
EOF
 
sudo systemctl enable --now ldap-sync.timer
 
# Add to crontab
(crontab -l; echo "*/15 * * * * python3 /opt/web-blocker/ad_ldap_sync.py") \
    | sudo crontab -
(crontab -l; echo "*/10 * * * * python3 /opt/web-blocker/report_dashboard.py") \
    | sudo crontab -

Complete System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                   Enterprise Web Security Platform               │
├──────────────────┬──────────────────────────────────────────────┤
│  Directory       │  Policy Engine          │  Dashboard          │
│                  │                         │                     │
│  Windows AD ─────┼─► ad_ldap_sync.py      │  /index.html        │
│  (Kerberos/NTLM) │      ↓                  │  /risk.html         │
│                  │  User → Dept map        │  /webreport.html ◄─┐│
│  OpenLDAP ───────┼─► Squid ACL generator  │  /webblocker.html  ││
│  (Basic Auth)    │      ↓                  │  /admin/requests   ││
│                  │  /etc/squid/            │                    ││
├──────────────────┤  departments/           │  nginx RBAC        ││
│  Employees       │  user_acls.conf         │  + SSL/TLS         ││
│                  │  dept_acls.conf         │                    ││
│  john (Eng) ─────┼─► engineering policy   │                    ││
│  mary (Mktg) ────┼─► marketing policy     │                    ││
│  raj  (Fin) ─────┼─► finance policy       │                    ││
│                  │      ↓                  │                    ││
├──────────────────┤  Squid Proxy            │  report_           ││
│  Blocked Page    │  (port 3128/3129)       │  dashboard.py ─────┘│
│                  │      ↓                  │                     │
│  Branded 403 ────┼─► Access Request Form  │                     │
│  + Request btn   │      ↓                  │                     │
│                  │  request_server.py      │                     │
│                  │  :8080                  │                     │
│                  │      ↓                  │                     │
│                  │  Manager email          │                     │
│                  │  [Approve] / [Deny]     │                     │
│                  │      ↓                  │                     │
│                  │  whitelist.txt updated  │                     │
│                  │  Squid reconfigured     │                     │
└──────────────────┴─────────────────────────┴─────────────────────┘

Dashboard Navigation

Page

URL

Access

Content

USB Audit

/index.html

Admin, SOC

USB events across all machines

Risk Scores

/risk.html

Admin, SOC

Per-user risk scoring

Web Report

/webreport.html

Admin, SOC

Blocked site analytics

Policy Status

/webblocker.html

Admin

Active block categories

Access Requests

/admin/requests

Admin, IT

Bypass request queue

Part 1: Automated Threat Response

Save as /opt/web-blocker/threat_response.py:

python

#!/usr/bin/env python3
"""
Automated Threat Response Engine
Monitors risk scores + web logs → auto-blocks users exceeding thresholds
"""
 
import sqlite3, json, os, subprocess, smtplib, time, logging
from datetime import datetime, timedelta
from collections import defaultdict, Counter
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
 
# ── Config ────────────────────────────────────────────────────────────
SMTP_SERVER  = "smtp.company.com"
SMTP_PORT    = 587
SMTP_USER    = "it-security@company.com"
SMTP_PASS    = "your_smtp_password"
IT_EMAIL     = "it-security@company.com"
SOC_EMAIL    = "soc@company.com"
 
RISK_DB      = "/var/lib/web-blocker/users.db"
RISK_SCORES  = "/var/usb-central/risk_scores.json"
SQUID_LOG    = "/var/log/squid/access.log"
BLOCK_LOG    = "/var/log/web-blocker/threat-response.log"
BLOCKED_USERS_FILE = "/etc/squid/blocked_users.txt"
AD_BLOCK_SCRIPT    = "/opt/web-blocker/ad_block_user.ps1"
 
os.makedirs(os.path.dirname(BLOCK_LOG), exist_ok=True)
 
logging.basicConfig(
    filename=BLOCK_LOG,
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s"
)
 
# ── Thresholds ────────────────────────────────────────────────────────
THRESHOLDS = {
    "risk_score_critical":     90,   # Auto-block if risk score ≥ 90
    "risk_score_high":         60,   # Alert IT if risk score ≥ 60
    "blocked_attempts_1h":     50,   # Alert if >50 blocked attempts/hr
    "blocked_attempts_15m":    20,   # Alert if >20 blocked attempts/15min
    "unique_blocked_domains":  15,   # Alert if >15 unique blocked domains/hr
    "usb_events_per_day":      10,   # Alert if >10 USB events/day
    "after_hours_attempts":     5,   # Alert if >5 after-hours blocked attempts
}
 
RESPONSE_ACTIONS = {
    "CRITICAL": ["squid_block", "ad_disable", "alert_soc",  "alert_manager"],
    "HIGH":     ["squid_block", "alert_soc",  "alert_manager"],
    "MEDIUM":   ["alert_it",    "increase_monitoring"],
    "LOW":      ["log_only"],
}
 
# ── Database ──────────────────────────────────────────────────────────
def init_response_db():
    conn = sqlite3.connect(RISK_DB)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS threat_responses (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            username    TEXT,
            threat_type TEXT,
            severity    TEXT,
            actions     TEXT,
            details     TEXT,
            timestamp   TEXT,
            resolved    INTEGER DEFAULT 0
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS blocked_users (
            username    TEXT PRIMARY KEY,
            reason      TEXT,
            blocked_at  TEXT,
            blocked_by  TEXT,
            auto_block  INTEGER DEFAULT 1,
            unblock_at  TEXT
        )
    """)
    conn.commit()
    conn.close()
 
def log_response(username, threat_type, severity, actions, details):
    conn = sqlite3.connect(RISK_DB)
    conn.execute("""
        INSERT INTO threat_responses
        (username, threat_type, severity, actions, details, timestamp)
        VALUES (?,?,?,?,?,?)
    """, (username, threat_type, severity,
          json.dumps(actions), details,
          datetime.now().isoformat()))
    conn.commit()
    conn.close()
    logging.info(f"RESPONSE | {username} | {severity} | {threat_type} | {actions}")
 
def is_already_blocked(username):
    conn = sqlite3.connect(RISK_DB)
    row  = conn.execute(
        "SELECT 1 FROM blocked_users WHERE username=?", (username,)
    ).fetchone()
    conn.close()
    return row is not None
 
def record_block(username, reason, auto=True, hours=24):
    unblock_at = (datetime.now() + timedelta(hours=hours)).isoformat()
    conn = sqlite3.connect(RISK_DB)
    conn.execute("""
        INSERT OR REPLACE INTO blocked_users
        (username, reason, blocked_at, blocked_by, auto_block, unblock_at)
        VALUES (?,?,?,?,?,?)
    """, (username, reason, datetime.now().isoformat(),
          "ThreatEngine", 1 if auto else 0, unblock_at))
    conn.commit()
    conn.close()
 
# ── Response Actions ──────────────────────────────────────────────────
def action_squid_block(username):
    """Block user in Squid proxy"""
    try:
        blocked = set()
        if os.path.exists(BLOCKED_USERS_FILE):
            with open(BLOCKED_USERS_FILE) as f:
                blocked = set(f.read().splitlines())
        blocked.add(username)
        with open(BLOCKED_USERS_FILE, "w") as f:
            f.write("\n".join(sorted(blocked)))
        subprocess.run(["squid", "-k", "reconfigure"],
                       capture_output=True, check=False)
        logging.info(f"SQUID_BLOCK | {username}")
        return True
    except Exception as e:
        logging.error(f"SQUID_BLOCK_FAIL | {username} | {e}")
        return False
 
def action_ad_disable(username):
    """Disable AD account via PowerShell (Windows DC)"""
    try:
        ps_cmd = f"""
        Disable-ADAccount -Identity '{username}' -Confirm:\$false
        Add-ADGroupMember -Identity 'Quarantine' -Members '{username}'
        Set-ADUser -Identity '{username}' `
            -Description 'AUTO-BLOCKED by ThreatEngine {datetime.now()}'
        """
        with open(AD_BLOCK_SCRIPT, "w") as f:
            f.write(ps_cmd)
        # Execute on DC via SSH or WinRM
        result = subprocess.run([
            "ssh", "Administrator@dc01.company.com",
            f"powershell -File {AD_BLOCK_SCRIPT}"
        ], capture_output=True, text=True, timeout=30)
        logging.info(f"AD_DISABLE | {username} | rc={result.returncode}")
        return result.returncode == 0
    except Exception as e:
        logging.error(f"AD_DISABLE_FAIL | {username} | {e}")
        return False
 
def action_linux_lock(username):
    """Lock Linux user account"""
    try:
        subprocess.run(["usermod", "-L", username], check=True)
        subprocess.run(["pkill", "-u", username], check=False)
        logging.info(f"LINUX_LOCK | {username}")
        return True
    except Exception as e:
        logging.error(f"LINUX_LOCK_FAIL | {username} | {e}")
        return False
 
def send_alert(to_list, subject, html_body):
    try:
        msg = MIMEMultipart("alternative")
        msg["Subject"] = subject
        msg["From"]    = SMTP_USER
        msg["To"]      = ", ".join(to_list)
        msg.attach(MIMEText(html_body, "html"))
        with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as s:
            s.starttls()
            s.login(SMTP_USER, SMTP_PASS)
            s.sendmail(SMTP_USER, to_list, msg.as_string())
        logging.info(f"ALERT_SENT | {to_list} | {subject}")
    except Exception as e:
        logging.error(f"ALERT_FAIL | {e}")
 
def build_alert_html(username, severity, threat_type, details,
                     actions_taken, score=None):
    color  = {"CRITICAL":"#e74c3c","HIGH":"#f39c12",
               "MEDIUM":"#3498db","LOW":"#2ecc71"}.get(severity,"#666")
    emoji  = {"CRITICAL":"🔴","HIGH":"🟡","MEDIUM":"🔵","LOW":"🟢"}.get(severity,"⚪")
    acts   = "".join(f"<li>{a}</li>" for a in actions_taken)
    score_row = (f"<tr style='background:#fff'>"
                 f"<td style='padding:8px;color:#666'>Risk Score</td>"
                 f"<td style='padding:8px;font-weight:bold'>{score}</td></tr>"
                 if score else "")
    return f"""
    <div style="font-family:sans-serif;max-width:620px;margin:auto">
      <div style="background:{color};color:white;padding:16px 22px;
                  border-radius:8px 8px 0 0">
        <h2 style="margin:0">{emoji} Threat Response — {severity}</h2>
      </div>
      <div style="background:#f9f9f9;padding:22px;border:1px solid #ddd">
        <table style="width:100%;border-collapse:collapse">
          <tr><td style="padding:8px;color:#666;width:140px">User</td>
              <td style="padding:8px;font-weight:bold">{username}</td></tr>
          <tr style="background:#fff">
              <td style="padding:8px;color:#666">Threat Type</td>
              <td style="padding:8px">{threat_type}</td></tr>
          <tr><td style="padding:8px;color:#666">Severity</td>
              <td style="padding:8px;font-weight:bold;color:{color}">{severity}</td></tr>
          {score_row}
          <tr style="background:#fff">
              <td style="padding:8px;color:#666">Time</td>
              <td style="padding:8px">{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</td></tr>
          <tr><td style="padding:8px;color:#666">Details</td>
              <td style="padding:8px">{details}</td></tr>
        </table>
        <div style="margin-top:18px">
          <strong>Actions Taken:</strong>
          <ul style="margin:8px 0 0 16px;color:#333">{acts}</ul>
        </div>
        <div style="margin-top:18px;padding:12px;background:#fff3cd;
                    border-radius:6px;font-size:0.88em;color:#856404">
          ⚠️ Review this alert in the
          <a href="https://usb-monitor.company.com/admin/requests">
          Security Dashboard</a> and take appropriate action.
        </div>
      </div>
      <div style="background:#eee;padding:10px 22px;font-size:0.78em;
                  color:#999;border-radius:0 0 8px 8px">
        Automated Threat Response Engine | Company IT Security
      </div>
    </div>"""
 
# ── Threat Detection ──────────────────────────────────────────────────
def check_risk_scores():
    """Check USB risk scores from risk engine"""
    threats = []
    try:
        if not os.path.exists(RISK_SCORES):
            return threats
        with open(RISK_SCORES) as f:
            scores = json.load(f)
        for username, data in scores.items():
            score = data.get("score", 0)
            if score >= THRESHOLDS["risk_score_critical"]:
                threats.append({
                    "username":   username,
                    "severity":   "CRITICAL",
                    "threat_type":"USB Risk Score Critical",
                    "details":    f"Risk score {score}/100 — "
                                  f"Violations: {', '.join(data.get('violations',[])[:3])}",
                    "score":      score,
                })
            elif score >= THRESHOLDS["risk_score_high"]:
                threats.append({
                    "username":   username,
                    "severity":   "HIGH",
                    "threat_type":"USB Risk Score High",
                    "details":    f"Risk score {score}/100",
                    "score":      score,
                })
    except Exception as e:
        logging.error(f"RISK_SCORE_CHECK | {e}")
    return threats
 
def check_web_log_anomalies():
    """Detect web log anomalies in last 15 minutes"""
    threats = []
    try:
        cutoff_15m = datetime.now() - timedelta(minutes=15)
        cutoff_1h  = datetime.now() - timedelta(hours=1)
 
        user_blocks_15m  = defaultdict(int)
        user_blocks_1h   = defaultdict(int)
        user_domains_1h  = defaultdict(set)
        after_hrs_blocks = defaultdict(int)
 
        import re
        pattern = re.compile(
            r'(\d+\.\d+)\s+\d+\s+\S+\s+(\w+)/(\d+)\s+\d+\s+\w+\s+(\S+)\s+(\S+)'
        )
 
        with open(SQUID_LOG, "r", errors="ignore") as f:
            for line in f:
                m = pattern.match(line)
                if not m: continue
                ts_raw, action, code, url, user = m.groups()
                ts   = datetime.fromtimestamp(float(ts_raw))
                user = user.replace("-", "")
                if not user: continue
                blocked = action in ("TCP_DENIED",) or int(code) == 403
 
                if blocked and ts >= cutoff_1h:
                    user_blocks_1h[user] += 1
                    dom = re.search(r'https?://([^/:]+)', url)
                    if dom: user_domains_1h[user].add(dom.group(1))
                    if ts.hour < 7 or ts.hour >= 20:
                        after_hrs_blocks[user] += 1
                if blocked and ts >= cutoff_15m:
                    user_blocks_15m[user] += 1
 
        # Evaluate thresholds
        all_users = set(user_blocks_1h) | set(user_blocks_15m)
        for user in all_users:
            b15  = user_blocks_15m.get(user, 0)
            b1h  = user_blocks_1h.get(user, 0)
            doms = len(user_domains_1h.get(user, set()))
            ah   = after_hrs_blocks.get(user, 0)
 
            if b15 >= THRESHOLDS["blocked_attempts_15m"]:
                threats.append({
                    "username":   user,
                    "severity":   "HIGH",
                    "threat_type":"Rapid Blocked Attempts",
                    "details":    f"{b15} blocked attempts in 15 min"
                                  f" | {doms} unique domains",
                    "score":      None,
                })
            elif b1h >= THRESHOLDS["blocked_attempts_1h"]:
                threats.append({
                    "username":   user,
                    "severity":   "MEDIUM",
                    "threat_type":"Excessive Blocked Attempts",
                    "details":    f"{b1h} blocked attempts in 1hr"
                                  f" | {doms} unique domains",
                    "score":      None,
                })
            if doms >= THRESHOLDS["unique_blocked_domains"]:
                threats.append({
                    "username":   user,
                    "severity":   "HIGH",
                    "threat_type":"Domain Enumeration / Data Exfil Attempt",
                    "details":    f"Attempted {doms} unique blocked domains in 1hr",
                    "score":      None,
                })
            if ah >= THRESHOLDS["after_hours_attempts"]:
                threats.append({
                    "username":   user,
                    "severity":   "MEDIUM",
                    "threat_type":"After-Hours Activity",
                    "details":    f"{ah} blocked attempts outside business hours",
                    "score":      None,
                })
    except FileNotFoundError:
        logging.warning("Squid log not found")
    except Exception as e:
        logging.error(f"WEB_LOG_CHECK | {e}")
    return threats
 
# ── Execute Response ──────────────────────────────────────────────────
def respond(threat):
    username    = threat["username"]
    severity    = threat["severity"]
    threat_type = threat["threat_type"]
    details     = threat["details"]
    score       = threat.get("score")
 
    if is_already_blocked(username):
        logging.info(f"SKIP | {username} already blocked")
        return
 
    actions_cfg    = RESPONSE_ACTIONS.get(severity, ["log_only"])
    actions_taken  = []
 
    for action in actions_cfg:
        if action == "squid_block":
            if action_squid_block(username):
                actions_taken.append("✅ Squid proxy access blocked")
                record_block(username, threat_type)
 
        elif action == "ad_disable":
            if action_ad_disable(username):
                actions_taken.append("✅ Active Directory account disabled")
            # Also lock Linux account
            if action_linux_lock(username):
                actions_taken.append("✅ Linux account locked")
 
        elif action == "alert_soc":
            html = build_alert_html(username, severity, threat_type,
                                    details, actions_taken, score)
            send_alert([SOC_EMAIL],
                       f"🔴 [{severity}] Threat Detected: {username}",
                       html)
            actions_taken.append("✅ SOC team alerted")
 
        elif action == "alert_manager":
            html = build_alert_html(username, severity, threat_type,
                                    details, actions_taken, score)
            send_alert([IT_EMAIL],
                       f"⚠️ [{severity}] Security Alert: {username}",
                       html)
            actions_taken.append("✅ IT manager alerted")
 
        elif action == "alert_it":
            html = build_alert_html(username, severity, threat_type,
                                    details, actions_taken, score)
            send_alert([IT_EMAIL],
                       f"ℹ️ [{severity}] Security Notice: {username}",
                       html)
            actions_taken.append("✅ IT team notified")
 
        elif action == "increase_monitoring":
            actions_taken.append("✅ Enhanced monitoring enabled")
 
        elif action == "log_only":
            actions_taken.append("📝 Event logged")
 
    log_response(username, threat_type, severity,
                 actions_taken, details)
    print(f"[{severity}] {username} — {threat_type} — {len(actions_taken)} actions")
 
# ── Main Loop ─────────────────────────────────────────────────────────
def run_once():
    init_response_db()
    print(f"[*] Threat scan — {datetime.now().strftime('%H:%M:%S')}")
    threats = check_risk_scores() + check_web_log_anomalies()
 
    # Deduplicate by username+threat_type
    seen, unique = set(), []
    for t in threats:
        key = f"{t['username']}|{t['threat_type']}"
        if key not in seen:
            seen.add(key)
            unique.append(t)
 
    print(f"[*] Threats found: {len(unique)}")
    for threat in unique:
        respond(threat)
 
def run_continuous(interval=60):
    print(f"[*] Threat Response Engine started (interval={interval}s)")
    while True:
        run_once()
        time.sleep(interval)
 
if __name__ == "__main__":
    import sys
    if "--daemon" in sys.argv:
        run_continuous(60)
    else:
        run_once()

Part 2: Weekly Executive Email Report

Save as /opt/web-blocker/executive_report.py:

python

#!/usr/bin/env python3
"""
Weekly Executive Security Report
Sends polished HTML email to leadership every Monday morning
"""
 
import sqlite3, json, os, re, smtplib
from datetime import datetime, timedelta
from collections import defaultdict, Counter
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
 
SMTP_SERVER = "smtp.company.com"
SMTP_PORT   = 587
SMTP_USER   = "it-security@company.com"
SMTP_PASS   = "your_smtp_password"
 
EXEC_LIST   = [
    "ceo@company.com",
    "cto@company.com",
    "ciso@company.com",
    "coo@company.com",
]
 
RISK_DB    = "/var/lib/web-blocker/users.db"
RISK_FILE  = "/var/usb-central/risk_scores.json"
SQUID_LOG  = "/var/log/squid/access.log"
SQUID_DIR  = "/var/usb-central/reports"
 
# ── Gather all stats ──────────────────────────────────────────────────
def gather_stats():
    now    = datetime.now()
    week_ago = now - timedelta(days=7)
    stats  = {
        "period_start": week_ago.strftime("%d %b %Y"),
        "period_end":   now.strftime("%d %b %Y"),
        "generated":    now.strftime("%d %b %Y, %H:%M"),
    }
 
    # ── Web stats ──
    total_req = blocked_req = 0
    top_blocked  = Counter()
    top_users_blk = Counter()
    daily_blocked = defaultdict(int)
    pattern = re.compile(
        r'(\d+\.\d+)\s+\d+\s+\S+\s+(\w+)/(\d+)\s+\d+\s+\w+\s+(\S+)\s+(\S+)'
    )
    try:
        with open(SQUID_LOG, "r", errors="ignore") as f:
            for line in f:
                m = pattern.match(line)
                if not m: continue
                ts_raw, action, code, url, user = m.groups()
                ts = datetime.fromtimestamp(float(ts_raw))
                if ts < week_ago: continue
                total_req += 1
                if action in ("TCP_DENIED",) or int(code) == 403:
                    blocked_req += 1
                    dom = re.search(r'https?://([^/:]+)', url)
                    if dom:
                        top_blocked[dom.group(1).lstrip("www.")] += 1
                    user = user.replace("-","")
                    if user: top_users_blk[user] += 1
                    daily_blocked[ts.strftime("%a %d %b")] += 1
    except: pass
 
    stats["web_total"]       = total_req
    stats["web_blocked"]     = blocked_req
    stats["web_allowed"]     = total_req - blocked_req
    stats["web_block_pct"]   = (round(blocked_req/total_req*100,1)
                                if total_req else 0)
    stats["top_blocked"]     = top_blocked.most_common(5)
    stats["top_violators"]   = top_users_blk.most_common(5)
    stats["daily_blocked"]   = list(daily_blocked.items())[-7:]
 
    # ── USB stats ──
    usb_total = usb_mounts = usb_files = 0
    try:
        import glob, csv
        for f in glob.glob(f"{SQUID_DIR}/**/*.csv", recursive=True):
            with open(f, newline="", errors="ignore") as fh:
                for row in csv.DictReader(fh):
                    try:
                        ts = datetime.strptime(
                            row.get("Timestamp","")[:19],
                            "%Y-%m-%d %H:%M:%S")
                        if ts < week_ago: continue
                    except: continue
                    usb_total += 1
                    evt = row.get("Event", row.get("EventType",""))
                    if "Mount" in evt:  usb_mounts += 1
                    if "File"  in evt:  usb_files  += 1
    except: pass
    stats["usb_total"]  = usb_total
    stats["usb_mounts"] = usb_mounts
    stats["usb_files"]  = usb_files
 
    # ── Risk scores ──
    critical_users = high_users = []
    try:
        with open(RISK_FILE) as f:
            scores = json.load(f)
        critical_users = [(u, d["score"]) for u,d in scores.items()
                          if d.get("level") == "CRITICAL"]
        high_users     = [(u, d["score"]) for u,d in scores.items()
                          if d.get("level") == "HIGH"]
    except: pass
    stats["critical_users"] = sorted(critical_users,
                                     key=lambda x:x[1], reverse=True)[:5]
    stats["high_users"]     = sorted(high_users,
                                     key=lambda x:x[1], reverse=True)[:5]
 
    # ── Threat responses ──
    threat_count = auto_blocks = 0
    try:
        conn = sqlite3.connect(RISK_DB)
        row  = conn.execute("""
            SELECT COUNT(*), SUM(CASE WHEN severity='CRITICAL' THEN 1 ELSE 0 END)
            FROM threat_responses
            WHERE timestamp >= ?
        """, (week_ago.isoformat(),)).fetchone()
        threat_count = row[0] or 0
        auto_blocks  = row[1] or 0
        conn.close()
    except: pass
    stats["threat_count"] = threat_count
    stats["auto_blocks"]  = auto_blocks
 
    # ── Access requests ──
    req_total = req_approved = req_denied = req_pending = 0
    try:
        conn = sqlite3.connect(RISK_DB)
        rows = conn.execute("""
            SELECT status, COUNT(*) FROM requests
            WHERE created_at >= ? GROUP BY status
        """, (week_ago.isoformat(),)).fetchall()
        for status, count in rows:
            req_total += count
            if status == "approved": req_approved += count
            elif status == "denied": req_denied   += count
            else:                    req_pending  += count
        conn.close()
    except: pass
    stats["req_total"]    = req_total
    stats["req_approved"] = req_approved
    stats["req_denied"]   = req_denied
    stats["req_pending"]  = req_pending
 
    return stats
 
# ── Build HTML Report ─────────────────────────────────────────────────
def build_report(stats):
 
    def stat_card(value, label, color="#e74c3c", sub=""):
        return f"""
        <td style="padding:12px;text-align:center;width:25%">
          <div style="background:#1a1d27;border-radius:10px;padding:18px 10px;
                      border-top:3px solid {color}">
            <div style="font-size:2em;font-weight:bold;color:{color}">{value}</div>
            <div style="font-size:0.8em;color:#aaa;margin-top:4px">{label}</div>
            {f'<div style="font-size:0.75em;color:#555;margin-top:3px">{sub}</div>' if sub else ''}
          </div>
        </td>"""
 
    def trend_row(label, value, color="#e0e0e0"):
        return f"""<tr>
          <td style="padding:9px 14px;border-bottom:1px solid #2c2f3e;
                     color:#aaa;font-size:0.88em">{label}</td>
          <td style="padding:9px 14px;border-bottom:1px solid #2c2f3e;
                     font-weight:bold;color:{color};font-size:0.88em">{value}</td>
        </tr>"""
 
    top_blocked_rows = "".join(
        f"""<tr>
          <td style="padding:7px 14px;border-bottom:1px solid #2c2f3e;
                     color:#e74c3c;font-size:0.85em">{dom}</td>
          <td style="padding:7px 14px;border-bottom:1px solid #2c2f3e;
                     font-size:0.85em">{cnt:,}</td>
        </tr>"""
        for dom, cnt in stats["top_blocked"]
    )
 
    top_violators_rows = "".join(
        f"""<tr>
          <td style="padding:7px 14px;border-bottom:1px solid #2c2f3e;
                     font-size:0.85em">👤 {user}</td>
          <td style="padding:7px 14px;border-bottom:1px solid #2c2f3e;
                     font-size:0.85em">{cnt:,}</td>
        </tr>"""
        for user, cnt in stats["top_violators"]
    )
 
    critical_rows = "".join(
        f"""<tr>
          <td style="padding:7px 14px;border-bottom:1px solid #2c2f3e;
                     font-size:0.85em">👤 {user}</td>
          <td style="padding:7px 14px;border-bottom:1px solid #2c2f3e;
                     font-weight:bold;color:#e74c3c;font-size:0.85em">{score}</td>
        </tr>"""
        for user, score in stats["critical_users"]
    ) or "<tr><td colspan='2' style='padding:10px;color:#555;font-size:0.85em'>None this week ✅</td></tr>"
 
    daily_bars = "".join(f"""
        <td style="text-align:center;vertical-align:bottom;padding:0 4px">
          <div style="background:#e74c3c;width:32px;
               height:{min(int(cnt/max(max(c for _,c in stats['daily_blocked']),1)*80),80)}px;
               border-radius:4px 4px 0 0;display:inline-block"></div>
          <div style="font-size:0.7em;color:#555;margin-top:4px">{day[:3]}</div>
          <div style="font-size:0.7em;color:#aaa">{cnt}</div>
        </td>"""
        for day, cnt in stats["daily_blocked"]
    )
 
    html = f"""<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body style="margin:0;padding:0;background:#0a0d14;
             font-family:Segoe UI,Helvetica,Arial,sans-serif">
 
  <!-- Header -->
  <div style="background:linear-gradient(135deg,#1a1d27 0%,#2c2f3e 100%);
              padding:32px 40px;border-bottom:3px solid #e74c3c">
    <table style="width:100%"><tr>
      <td>
        <div style="color:#e74c3c;font-size:1.5em;font-weight:bold">
          🛡 Weekly Security Report
        </div>
        <div style="color:#888;font-size:0.85em;margin-top:4px">
          {stats['period_start']} — {stats['period_end']}
        </div>
      </td>
      <td style="text-align:right;color:#555;font-size:0.8em">
        Generated: {stats['generated']}<br>
        IT Security Team
      </td>
    </tr></table>
  </div>
 
  <div style="padding:30px 40px;background:#0f1117">
 
    <!-- Executive Summary -->
    <div style="color:#e74c3c;font-size:0.8em;font-weight:bold;
                letter-spacing:2px;text-transform:uppercase;margin-bottom:12px">
      Executive Summary
    </div>
    <table style="width:100%;border-collapse:collapse;margin-bottom:28px">
      <tr>
        {stat_card(f"{stats['web_blocked']:,}", "Web Requests Blocked", "#e74c3c",
                   f"{stats['web_block_pct']}% block rate")}
        {stat_card(f"{stats['usb_total']:,}", "USB Events", "#f39c12",
                   f"{stats['usb_mounts']} mounts")}
        {stat_card(f"{stats['threat_count']:,}", "Threats Detected", "#9b59b6",
                   f"{stats['auto_blocks']} auto-blocked")}
        {stat_card(f"{len(stats['critical_users'])}", "Critical Risk Users", "#e74c3c",
                   f"{len(stats['high_users'])} high risk")}
      </tr>
    </table>
 
    <!-- 2-column layout -->
    <table style="width:100%;border-collapse:collapse;
                  margin-bottom:24px;vertical-align:top">
    <tr><td style="width:50%;padding-right:12px;vertical-align:top">
 
      <!-- Web Blocking -->
      <div style="background:#1a1d27;border-radius:10px;
                  padding:20px;margin-bottom:16px">
        <div style="color:#e74c3c;font-size:0.82em;font-weight:bold;
                    letter-spacing:1px;text-transform:uppercase;margin-bottom:14px">
          🌐 Web Blocking
        </div>
        <table style="width:100%;border-collapse:collapse">
          {trend_row("Total Requests",  f"{stats['web_total']:,}")}
          {trend_row("Blocked",         f"{stats['web_blocked']:,}", "#e74c3c")}
          {trend_row("Allowed",         f"{stats['web_allowed']:,}", "#2ecc71")}
          {trend_row("Block Rate",      f"{stats['web_block_pct']}%", "#f39c12")}
        </table>
      </div>
 
      <!-- Top Blocked Sites -->
      <div style="background:#1a1d27;border-radius:10px;padding:20px">
        <div style="color:#e74c3c;font-size:0.82em;font-weight:bold;
                    letter-spacing:1px;text-transform:uppercase;margin-bottom:14px">
          🚫 Top Blocked Sites
        </div>
        <table style="width:100%;border-collapse:collapse">
          <tr>
            <th style="padding:7px 14px;text-align:left;color:#555;
                       font-size:0.78em;border-bottom:1px solid #2c2f3e">Domain</th>
            <th style="padding:7px 14px;text-align:left;color:#555;
                       font-size:0.78em;border-bottom:1px solid #2c2f3e">Attempts</th>
          </tr>
          {top_blocked_rows}
        </table>
      </div>
 
    </td><td style="width:50%;padding-left:12px;vertical-align:top">
 
      <!-- Access Requests -->
      <div style="background:#1a1d27;border-radius:10px;
                  padding:20px;margin-bottom:16px">
        <div style="color:#3498db;font-size:0.82em;font-weight:bold;
                    letter-spacing:1px;text-transform:uppercase;margin-bottom:14px">
          🔓 Access Requests
        </div>
        <table style="width:100%;border-collapse:collapse">
          {trend_row("Total Submitted",  stats['req_total'])}
          {trend_row("Approved",         stats['req_approved'], "#2ecc71")}
          {trend_row("Denied",           stats['req_denied'],   "#e74c3c")}
          {trend_row("Pending Review",   stats['req_pending'],  "#f39c12")}
        </table>
      </div>
 
      <!-- Top Policy Violators -->
      <div style="background:#1a1d27;border-radius:10px;padding:20px">
        <div style="color:#f39c12;font-size:0.82em;font-weight:bold;
                    letter-spacing:1px;text-transform:uppercase;margin-bottom:14px">
          ⚠️ Top Policy Violators
        </div>
        <table style="width:100%;border-collapse:collapse">
          <tr>
            <th style="padding:7px 14px;text-align:left;color:#555;
                       font-size:0.78em;border-bottom:1px solid #2c2f3e">User</th>
            <th style="padding:7px 14px;text-align:left;color:#555;
                       font-size:0.78em;border-bottom:1px solid #2c2f3e">Attempts</th>
          </tr>
          {top_violators_rows}
        </table>
      </div>
 
    </td></tr></table>
 
    <!-- Daily Trend Bar Chart -->
    <div style="background:#1a1d27;border-radius:10px;
                padding:22px;margin-bottom:24px">
      <div style="color:#e74c3c;font-size:0.82em;font-weight:bold;
                  letter-spacing:1px;text-transform:uppercase;margin-bottom:16px">
        📅 Daily Blocked Requests (Last 7 Days)
      </div>
      <table style="width:100%"><tr style="vertical-align:bottom">
        {daily_bars}
      </tr></table>
    </div>
 
    <!-- Critical Risk Users -->
    <div style="background:#1a1d27;border-radius:10px;padding:22px">
      <div style="color:#e74c3c;font-size:0.82em;font-weight:bold;
                  letter-spacing:1px;text-transform:uppercase;margin-bottom:14px">
        🔴 Critical Risk Users — Immediate Attention Required
      </div>
      <table style="width:100%;border-collapse:collapse">
        <tr>
          <th style="padding:8px 14px;text-align:left;color:#555;
                     font-size:0.78em;border-bottom:1px solid #2c2f3e">User</th>
          <th style="padding:8px 14px;text-align:left;color:#555;
                     font-size:0.78em;border-bottom:1px solid #2c2f3e">Risk Score</th>
        </tr>
        {critical_rows}
      </table>
    </div>
 
  </div>
 
  <!-- Footer -->
  <div style="background:#1a1d27;padding:18px 40px;
              border-top:1px solid #2c2f3e;
              font-size:0.78em;color:#444;text-align:center">
    🛡 Company IT Security &nbsp;|&nbsp;
    <a href="https://usb-monitor.company.com"
       style="color:#3498db;text-decoration:none">Open Dashboard</a>
    &nbsp;|&nbsp; Auto-generated weekly report
  </div>
 
</body>
</html>"""
    return html
 
def send_report():
    print("[*] Gathering weekly stats...")
    stats = gather_stats()
    html  = build_report(stats)
 
    msg = MIMEMultipart("alternative")
    msg["Subject"] = (f"📊 Weekly Security Report — "
                      f"{stats['period_start']} to {stats['period_end']}")
    msg["From"]    = SMTP_USER
    msg["To"]      = ", ".join(EXEC_LIST)
    msg.attach(MIMEText(html, "html"))
 
    with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as s:
        s.starttls()
        s.login(SMTP_USER, SMTP_PASS)
        s.sendmail(SMTP_USER, EXEC_LIST, msg.as_string())
 
    print(f"[✔] Executive report sent to {len(EXEC_LIST)} recipients")
 
if __name__ == "__main__":
    send_report()

Part 3: Mobile Push Notifications

Save as /opt/web-blocker/push_notifier.py:

python

#!/usr/bin/env python3
"""
Mobile Push Notification System
Sends real-time alerts via FCM (Android), APNs (iOS), and Slack
"""
 
import json, sqlite3, os, time, requests, logging
from datetime import datetime
 
# ── Config ────────────────────────────────────────────────────────────
FCM_SERVER_KEY   = "your_fcm_server_key"     # Firebase Cloud Messaging
FCM_URL          = "https://fcm.googleapis.com/fcm/send"
 
APNS_KEY_FILE    = "/opt/web-blocker/apns/AuthKey_XXXXXXXX.p8"
APNS_KEY_ID      = "YOUR_KEY_ID"
APNS_TEAM_ID     = "YOUR_TEAM_ID"
APNS_BUNDLE_ID   = "com.company.securityapp"
 
SLACK_WEBHOOK    = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
TEAMS_WEBHOOK    = "https://company.webhook.office.com/webhookb2/YOUR/TEAMS/WEBHOOK"
 
PUSH_DB          = "/var/lib/web-blocker/push_tokens.db"
PUSH_LOG         = "/var/log/web-blocker/push.log"
 
os.makedirs(os.path.dirname(PUSH_LOG), exist_ok=True)
logging.basicConfig(filename=PUSH_LOG, level=logging.INFO,
                    format="%(asctime)s %(message)s")
 
# ── Token Database ────────────────────────────────────────────────────
def init_push_db():
    conn = sqlite3.connect(PUSH_DB)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS push_tokens (
            username    TEXT PRIMARY KEY,
            fcm_token   TEXT,
            apns_token  TEXT,
            platform    TEXT,
            role        TEXT,
            registered  TEXT
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS push_log (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            username    TEXT,
            platform    TEXT,
            title       TEXT,
            body        TEXT,
            severity    TEXT,
            sent_at     TEXT,
            success     INTEGER
        )
    """)
    conn.commit()
    conn.close()
 
def register_token(username, platform, token, role="soc"):
    conn = sqlite3.connect(PUSH_DB)
    if platform == "android":
        conn.execute("""
            INSERT OR REPLACE INTO push_tokens
            (username, fcm_token, platform, role, registered)
            VALUES (?,?,?,?,?)
        """, (username, token, platform, role, datetime.now().isoformat()))
    else:
        conn.execute("""
            INSERT OR REPLACE INTO push_tokens
            (username, apns_token, platform, role, registered)
            VALUES (?,?,?,?,?)
        """, (username, token, platform, role, datetime.now().isoformat()))
    conn.commit()
    conn.close()
 
def get_soc_tokens():
    conn = sqlite3.connect(PUSH_DB)
    conn.row_factory = sqlite3.Row
    rows = conn.execute(
        "SELECT * FROM push_tokens WHERE role IN ('soc','admin')"
    ).fetchall()
    conn.close()
    return [dict(r) for r in rows]
 
def log_push(username, platform, title, body, severity, success):
    conn = sqlite3.connect(PUSH_DB)
    conn.execute("""
        INSERT INTO push_log (username, platform, title, body, severity, sent_at, success)
        VALUES (?,?,?,?,?,?,?)
    """, (username, platform, title, body, severity,
          datetime.now().isoformat(), 1 if success else 0))
    conn.commit()
    conn.close()
 
# ── FCM (Android) ─────────────────────────────────────────────────────
def send_fcm(token, title, body, severity, data=None):
    priority_map = {"CRITICAL":"high", "HIGH":"high",
                    "MEDIUM":"normal", "LOW":"normal"}
    color_map    = {"CRITICAL":"#e74c3c","HIGH":"#f39c12",
                    "MEDIUM":"#3498db","LOW":"#2ecc71"}
    payload = {
        "to": token,
        "priority": priority_map.get(severity, "normal"),
        "notification": {
            "title":  title,
            "body":   body,
            "icon":   "security_alert",
            "color":  color_map.get(severity, "#e74c3c"),
            "sound":  "alarm" if severity == "CRITICAL" else "default",
            "badge":  1,
            "click_action": "SECURITY_DASHBOARD"
        },
        "data": {
            "severity":   severity,
            "timestamp":  datetime.now().isoformat(),
            "dashboard":  "https://usb-monitor.company.com",
            **(data or {})
        }
    }
    try:
        resp = requests.post(
            FCM_URL,
            json=payload,
            headers={"Authorization": f"key={FCM_SERVER_KEY}",
                     "Content-Type": "application/json"},
            timeout=10
        )
        success = resp.status_code == 200
        logging.info(f"FCM | {title} | {severity} | {resp.status_code}")
        return success
    except Exception as e:
        logging.error(f"FCM_ERROR | {e}")
        return False
 
# ── APNs (iOS) ────────────────────────────────────────────────────────
def send_apns(token, title, body, severity):
    """
    APNs via HTTP/2 using jwt auth.
    Requires: pip3 install PyJWT cryptography
    """
    try:
        import jwt, time
        from cryptography.hazmat.primitives.serialization import load_pem_private_key
 
        with open(APNS_KEY_FILE, "rb") as f:
            private_key = load_pem_private_key(f.read(), password=None)
 
        auth_token = jwt.encode(
            {"iss": APNS_TEAM_ID, "iat": int(time.time())},
            private_key,
            algorithm="ES256",
            headers={"kid": APNS_KEY_ID}
        )
 
        priority = "10" if severity in ("CRITICAL","HIGH") else "5"
        payload  = {
            "aps": {
                "alert":            {"title": title, "body": body},
                "badge":            1,
                "sound":            "alarm.aiff" if severity == "CRITICAL"
                                    else "default",
                "interruption-level": "critical" if severity == "CRITICAL"
                                      else "active"
            },
            "severity":  severity,
            "dashboard": "https://usb-monitor.company.com"
        }
 
        resp = requests.post(
            f"https://api.push.apple.com/3/device/{token}",
            json=payload,
            headers={
                "Authorization":  f"bearer {auth_token}",
                "apns-topic":     APNS_BUNDLE_ID,
                "apns-priority":  priority,
                "apns-push-type": "alert"
            },
            timeout=10
        )
        success = resp.status_code == 200
        logging.info(f"APNs | {title} | {severity} | {resp.status_code}")
        return success
    except Exception as e:
        logging.error(f"APNs_ERROR | {e}")
        return False
 
# ── Slack Push ────────────────────────────────────────────────────────
def send_slack_push(title, body, severity, details=None):
    color_map = {"CRITICAL":"danger","HIGH":"warning",
                 "MEDIUM":"#3498db","LOW":"good"}
    emoji_map = {"CRITICAL":"🔴","HIGH":"🟡","MEDIUM":"🔵","LOW":"🟢"}
    fields    = [{"title":"Severity","value":severity,"short":True},
                 {"title":"Time","value":datetime.now().strftime("%H:%M:%S"),"short":True}]
    if details:
        fields += [{"title":k,"value":v,"short":True}
                   for k,v in details.items()]
    payload = {
        "attachments": [{
            "color":   color_map.get(severity,"#666"),
            "title":   f"{emoji_map.get(severity,'')} {title}",
            "text":    body,
            "fields":  fields,
            "actions": [{
                "type":  "button",
                "text":  "Open Dashboard",
                "url":   "https://usb-monitor.company.com",
                "style": "danger" if severity=="CRITICAL" else "default"
            }],
            "footer": "Security Monitor",
            "ts":     int(time.time())
        }]
    }
    try:
        resp = requests.post(SLACK_WEBHOOK, json=payload, timeout=10)
        logging.info(f"SLACK | {title} | {resp.status_code}")
        return resp.status_code == 200
    except Exception as e:
        logging.error(f"SLACK_ERROR | {e}")
        return False
 
# ── MS Teams Push ─────────────────────────────────────────────────────
def send_teams_push(title, body, severity, details=None):
    color_map = {"CRITICAL":"FF0000","HIGH":"FFA500",
                 "MEDIUM":"0076D7","LOW":"00CC00"}
    facts     = [{"name":k,"value":v} for k,v in (details or {}).items()]
    payload   = {
        "@type":      "MessageCard",
        "@context":   "http://schema.org/extensions",
        "themeColor": color_map.get(severity,"FF0000"),
        "summary":    title,
        "sections":   [{
            "activityTitle":    f"🛡 {title}",
            "activitySubtitle": f"Severity: **{severity}**",
            "activityText":     body,
            "facts":            facts
        }],
        "potentialAction": [{
            "@type": "OpenUri",
            "name":  "Open Security Dashboard",
            "targets": [{"os":"default",
                         "uri":"https://usb-monitor.company.com"}]
        }]
    }
    try:
        resp = requests.post(TEAMS_WEBHOOK, json=payload, timeout=10)
        logging.info(f"TEAMS | {title} | {resp.status_code}")
        return resp.status_code in (200,202)
    except Exception as e:
        logging.error(f"TEAMS_ERROR | {e}")
        return False
 
# ── Main Push Dispatcher ──────────────────────────────────────────────
def push_alert(title, body, severity,
               details=None, mobile=True, slack=True, teams=True):
    """
    Send push notifications to all SOC staff + Slack + Teams
    Call this from threat_response.py for critical events
    """
    init_push_db()
    results = []
 
    # Mobile push to all registered SOC devices
    if mobile:
        tokens = get_soc_tokens()
        for t in tokens:
            if t.get("fcm_token"):
                ok = send_fcm(t["fcm_token"], title, body, severity, details)
                log_push(t["username"], "android", title, body, severity, ok)
                results.append(("FCM", t["username"], ok))
            if t.get("apns_token"):
                ok = send_apns(t["apns_token"], title, body, severity)
                log_push(t["username"], "ios", title, body, severity, ok)
                results.append(("APNs", t["username"], ok))
 
    # Slack
    if slack:
        ok = send_slack_push(title, body, severity, details)
        results.append(("Slack", "channel", ok))
 
    # Teams
    if teams:
        ok = send_teams_push(title, body, severity, details)
        results.append(("Teams", "channel", ok))
 
    sent = sum(1 for _,_,ok in results if ok)
    logging.info(f"PUSH_DONE | {title} | {severity} | {sent}/{len(results)} sent")
    print(f"[PUSH] {title} | {severity} | {sent}/{len(results)} delivered")
    return results
 
# ── Token Registration API ────────────────────────────────────────────
def registration_server():
    """Simple HTTP endpoint for app token registration"""
    from http.server import HTTPServer, BaseHTTPRequestHandler
    import json
 
    class Handler(BaseHTTPRequestHandler):
        def log_message(self, *args): pass
        def do_POST(self):
            if self.path == "/register-push":
                length = int(self.headers.get("Content-Length",0))
                data   = json.loads(self.rfile.read(length))
                register_token(
                    data["username"],
                    data["platform"],
                    data["token"],
                    data.get("role","soc")
                )
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'{"status":"ok"}')
                logging.info(f"TOKEN_REG | {data['username']} | {data['platform']}")
 
    print("[*] Push token registration server on :8081")
    HTTPServer(("0.0.0.0", 8081), Handler).serve_forever()
 
if __name__ == "__main__":
    import sys
    if "--register-server" in sys.argv:
        registration_server()
    else:
        # Test push
        push_alert(
            title    = "🔴 Test Security Alert",
            body     = "This is a test push notification from the security platform.",
            severity = "HIGH",
            details  = {"Host":"proxy-server","Test":"true"}
        )

Wire Everything Together

python

# Add to threat_response.py — import and call push_notifier
from push_notifier import push_alert
 
# In respond() function, add to CRITICAL/HIGH actions:
def respond(threat):
    ...
    if severity in ("CRITICAL", "HIGH"):
        push_alert(
            title    = f"🔴 {threat['threat_type']}",
            body     = f"User: {username} — {details[:100]}",
            severity = severity,
            details  = {
                "User":       username,
                "Risk Score": str(score or "N/A"),
                "Machine":    os.uname().nodename
            }
        )

Deploy All Three

bash

# Install dependencies
pip3 install requests PyJWT cryptography ldap3 --break-system-packages
 
# Deploy threat response as daemon
sudo tee /etc/systemd/system/threat-response.service <<EOF
[Unit]
Description=Automated Threat Response Engine
After=network.target squid.service
[Service]
ExecStart=/usr/bin/python3 /opt/web-blocker/threat_response.py --daemon
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now threat-response
 
# Deploy push registration server
sudo tee /etc/systemd/system/push-register.service <<EOF
[Unit]
Description=Push Token Registration Server
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/web-blocker/push_notifier.py --register-server
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now push-register
 
# Schedule weekly executive report — every Monday 07:00
echo "0 7 * * MON python3 /opt/web-blocker/executive_report.py" \
    | sudo crontab -
 
# Test everything
python3 /opt/web-blocker/threat_response.py        # Single scan
python3 /opt/web-blocker/executive_report.py       # Send report now
python3 /opt/web-blocker/push_notifier.py          # Test push

Complete Platform Summary

┌──────────────────────────────────────────────────────────────────┐
│            Enterprise Security Platform — Full Stack              │
├────────────────────┬─────────────────────────────────────────────┤
│  Detection         │  Response               │  Reporting         │
│                    │                         │                    │
│  USB Monitor  ─────┼─► Threat Engine         │  Live Dashboard    │
│  Web Proxy    ─────┼─►  ├─ Squid block       │  Risk Scores       │
│  Risk Scores  ─────┼─►  ├─ AD disable        │  Web Analytics     │
│  Log Analysis ─────┼─►  ├─ Linux lock        │  Access Requests   │
│                    │    ├─ Email SOC/Mgr  ───►│                    │
│                    │    ├─ Slack alert    ───►│  Weekly Exec Email │
│                    │    ├─ Teams alert    ───►│  (Every Monday)    │
│                    │    └─ Mobile push   ───►│                    │
│                    │       ├─ Android FCM     │  Push Alerts       │
│                    │       ├─ iOS APNs        │  ├─ Android        │
│                    │       └─ Auto-unblock    │  ├─ iOS            │
│                    │          after 24h       │  ├─ Slack          │
│                    │                         │  └─ MS Teams       │
└────────────────────┴─────────────────────────┴────────────────────┘







Here's the complete unified installer and configuration UI for both distros.


Part 1: Unified Installer Script

Save as install.sh and run as root:

bash

#!/bin/bash
# ================================================================
# Enterprise Security Platform — Unified Installer
# Supports: Ubuntu/Debian + RHEL/CentOS/Fedora
# Components: Squid, nginx, Python services, systemd units
# ================================================================
 
set -euo pipefail
 
# ── Colors ────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
 
log()     { echo -e "${GREEN}[✔]${NC} $1"; }
warn()    { echo -e "${YELLOW}[!]${NC} $1"; }
error()   { echo -e "${RED}[✘]${NC} $1"; exit 1; }
section() { echo -e "\n${CYAN}══════════════════════════════════════${NC}"; \
            echo -e "${CYAN}  $1${NC}"; \
            echo -e "${CYAN}══════════════════════════════════════${NC}"; }
 
# ── Root check ────────────────────────────────────────────────────────
[ "$EUID" -ne 0 ] && error "Run as root: sudo bash install.sh"
 
# ── Detect distro ─────────────────────────────────────────────────────
if   [ -f /etc/debian_version ]; then DISTRO="debian"
elif [ -f /etc/redhat-release ]; then DISTRO="rhel"
else error "Unsupported distro"; fi
log "Detected distro: $DISTRO"
 
# ── Load config ───────────────────────────────────────────────────────
CONFIG_FILE="./install.conf"
if [ ! -f "$CONFIG_FILE" ]; then
cat > "$CONFIG_FILE" <<'CONF'
# ── Company Settings ──────────────────────────────────────
COMPANY_NAME="Company Name"
DOMAIN="company.com"
AD_SERVER="dc01.company.com"
LDAP_SERVER="ldap.company.com"
 
# ── Network ───────────────────────────────────────────────
PROXY_PORT=3128
DASHBOARD_PORT=443
REQUEST_SERVER_PORT=8080
PUSH_SERVER_PORT=8081
ADMIN_UI_PORT=8443
LAN_NETWORK="192.168.0.0/16"
 
# ── Email ─────────────────────────────────────────────────
SMTP_SERVER="smtp.company.com"
SMTP_PORT=587
SMTP_USER="it-security@company.com"
SMTP_PASS="changeme"
IT_EMAIL="it-security@company.com"
SOC_EMAIL="soc@company.com"
 
# ── Integrations ──────────────────────────────────────────
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/HOOK"
TEAMS_WEBHOOK="https://company.webhook.office.com/YOUR/HOOK"
FCM_SERVER_KEY="your_fcm_key"
 
# ── Install paths ─────────────────────────────────────────
INSTALL_DIR="/opt/security-platform"
DATA_DIR="/var/lib/security-platform"
LOG_DIR="/var/log/security-platform"
WEB_DIR="/var/www/html/security"
CONF
 
    warn "Config file created: $CONFIG_FILE"
    warn "Edit it before continuing, then re-run."
    exit 0
fi
 
source "$CONFIG_FILE"
log "Config loaded from $CONFIG_FILE"
 
# ── Directories ───────────────────────────────────────────────────────
section "Creating directories"
mkdir -p "$INSTALL_DIR"/{scripts,certs,keys}
mkdir -p "$DATA_DIR"/{db,reports,usb-reports}
mkdir -p "$LOG_DIR"
mkdir -p "$WEB_DIR"
mkdir -p /etc/squid/departments
mkdir -p /etc/nginx/usb-auth
log "Directories created"
 
# ── Package installation ──────────────────────────────────────────────
section "Installing packages"
if [ "$DISTRO" = "debian" ]; then
    apt-get update -qq
    apt-get install -y \
        squid squid-openssl nginx python3 python3-pip \
        sqlite3 curl wget git openssl \
        krb5-user samba winbind libpam-winbind \
        libnss-winbind apache2-utils rsync \
        inotify-tools auditd audispd-plugins \
        dnsmasq mailutils smbclient nssm 2>/dev/null || true
else
    dnf install -y epel-release 2>/dev/null || \
        yum install -y epel-release 2>/dev/null
    dnf install -y \
        squid nginx python3 python3-pip \
        sqlite curl wget git openssl \
        krb5-workstation samba-winbind \
        httpd-tools rsync \
        inotify-tools audit \
        dnsmasq mailx 2>/dev/null || \
    yum install -y \
        squid nginx python3 python3-pip \
        sqlite curl wget git openssl \
        krb5-workstation samba-winbind \
        httpd-tools rsync inotify-tools audit dnsmasq
fi
log "System packages installed"
 
# ── Python packages ───────────────────────────────────────────────────
section "Installing Python packages"
pip3 install --break-system-packages --quiet \
    ldap3 requests PyJWT cryptography \
    flask flask-login flask-wtf \
    apscheduler paramiko 2>/dev/null || \
pip3 install --quiet \
    ldap3 requests PyJWT cryptography \
    flask flask-login flask-wtf \
    apscheduler paramiko
log "Python packages installed"
 
# ── SSL Certificate ───────────────────────────────────────────────────
section "Generating SSL certificate"
CERT_DIR="$INSTALL_DIR/certs"
openssl req -x509 -nodes -days 3650 \
    -newkey rsa:4096 \
    -keyout "$CERT_DIR/server.key" \
    -out    "$CERT_DIR/server.crt" \
    -subj   "/C=IN/ST=Telangana/L=Hyderabad/O=$COMPANY_NAME/CN=security.$DOMAIN" \
    2>/dev/null
chmod 600 "$CERT_DIR/server.key"
log "SSL certificate generated (10 year)"
 
# ── Copy platform scripts ─────────────────────────────────────────────
section "Installing platform scripts"
SCRIPTS=(
    "usb_alerter.py"
    "ad_ldap_sync.py"
    "dept_policies.py"
    "threat_response.py"
    "executive_report.py"
    "push_notifier.py"
    "request_server.py"
    "risk_engine.py"
    "report_dashboard.py"
    "generate_dashboard.py"
    "admin_ui.py"
)
for script in "${SCRIPTS[@]}"; do
    [ -f "./$script" ] && cp "./$script" "$INSTALL_DIR/scripts/" \
        && chmod +x "$INSTALL_DIR/scripts/$script" \
        && log "Installed: $script" \
        || warn "Missing: $script (install manually)"
done
 
# Inject config into all scripts
for script in "$INSTALL_DIR"/scripts/*.py; do
    sed -i "s|smtp.company.com|$SMTP_SERVER|g"   "$script" 2>/dev/null || true
    sed -i "s|company.com|$DOMAIN|g"             "$script" 2>/dev/null || true
    sed -i "s|it-security@company.com|$IT_EMAIL|g" "$script" 2>/dev/null || true
    sed -i "s|soc@company.com|$SOC_EMAIL|g"       "$script" 2>/dev/null || true
    sed -i "s|your_fcm_key|$FCM_SERVER_KEY|g"    "$script" 2>/dev/null || true
done
log "Config injected into all scripts"
 
# ── Squid config ──────────────────────────────────────────────────────
section "Configuring Squid proxy"
cp /etc/squid/squid.conf /etc/squid/squid.conf.bak 2>/dev/null || true
cat > /etc/squid/squid.conf <<SQUID
# ── Enterprise Squid Config — auto-generated ──
http_port $PROXY_PORT
acl localnet src $LAN_NETWORK
acl SSL_ports port 443
acl Safe_ports port 80 443 8080 21 70 210 1025-65535
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
 
# Blocked users list
acl blocked_users proxy_auth "/etc/squid/blocked_users.txt"
http_access deny blocked_users
 
# Department ACLs (auto-generated)
include /etc/squid/departments/dept_acls.conf
include /etc/squid/departments/user_acls.conf
 
# Approved exceptions whitelist
acl whitelist dstdomain "/etc/squid/whitelist.txt"
http_access allow whitelist
 
# Blocked domains
acl blocked_sites dstdomain "/etc/squid/blocked_domains.txt"
http_access deny blocked_sites
 
http_access allow localnet
http_access deny all
 
# Block page
deny_info https://security.$DOMAIN/blocked/?url=%u all
 
# Logging
access_log /var/log/squid/access.log combined
cache_log  /var/log/squid/cache.log
cache_mem 256 MB
SQUID
 
# Create empty list files
touch /etc/squid/blocked_users.txt
touch /etc/squid/whitelist.txt
 
# Basic blocked domains
cat > /etc/squid/blocked_domains.txt <<DOMAINS
gmail.com
mail.google.com
hotmail.com
yahoo.com
protonmail.com
facebook.com
twitter.com
x.com
instagram.com
tiktok.com
snapchat.com
reddit.com
dropbox.com
wetransfer.com
mega.nz
youtube.com
netflix.com
twitch.tv
spotify.com
DOMAINS
 
touch /etc/squid/departments/dept_acls.conf
touch /etc/squid/departments/user_acls.conf
 
squid -k parse && log "Squid config valid" || warn "Squid config has errors"
systemctl enable squid && systemctl restart squid
log "Squid started"
 
# ── nginx config ──────────────────────────────────────────────────────
section "Configuring nginx"
 
# RBAC password files
htpasswd -cb /etc/nginx/usb-auth/admins.htpasswd    admin       "$(openssl rand -base64 12)"
htpasswd -cb /etc/nginx/usb-auth/auditors.htpasswd  auditor     "$(openssl rand -base64 12)"
htpasswd -cb /etc/nginx/usb-auth/managers.htpasswd  manager     "$(openssl rand -base64 12)"
 
NGINX_CONF="/etc/nginx/sites-available/security-platform"
[ "$DISTRO" = "rhel" ] && NGINX_CONF="/etc/nginx/conf.d/security-platform.conf"
 
cat > "$NGINX_CONF" <<NGINX
server {
    listen 443 ssl;
    server_name security.$DOMAIN;
 
    ssl_certificate     $CERT_DIR/server.crt;
    ssl_certificate_key $CERT_DIR/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
 
    root  $WEB_DIR;
    index index.html;
 
    # Admin UI (full access)
    location /admin/ {
        proxy_pass http://127.0.0.1:$ADMIN_UI_PORT/;
        proxy_set_header Host \$host;
        auth_basic           "Admin UI";
        auth_basic_user_file /etc/nginx/usb-auth/admins.htpasswd;
    }
 
    # Dashboard — admins + SOC
    location / {
        auth_basic           "Security Dashboard";
        auth_basic_user_file /etc/nginx/usb-auth/admins.htpasswd;
        try_files \$uri \$uri/ =404;
    }
 
    # Risk report — admins only
    location /risk.html {
        auth_basic           "Risk Report";
        auth_basic_user_file /etc/nginx/usb-auth/admins.htpasswd;
    }
 
    # Block page — public (no auth)
    location /blocked/ {
        auth_basic off;
        try_files \$uri \$uri/ =404;
    }
 
    # Access request API — public
    location /api/ {
        auth_basic off;
        proxy_pass http://127.0.0.1:$REQUEST_SERVER_PORT/api/;
    }
 
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header Strict-Transport-Security "max-age=31536000";
}
 
server {
    listen 80;
    server_name security.$DOMAIN;
    return 301 https://\$host\$request_uri;
}
NGINX
 
if [ "$DISTRO" = "debian" ]; then
    ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/ 2>/dev/null || true
    rm -f /etc/nginx/sites-enabled/default
fi
 
nginx -t && log "nginx config valid" || warn "nginx config has errors"
systemctl enable nginx && systemctl restart nginx
log "nginx started"
 
# ── auditd for USB ────────────────────────────────────────────────────
section "Configuring auditd (USB monitoring)"
cat > /etc/audit/rules.d/usb.rules <<'AUDIT'
-a always,exit -F arch=b64 -S mount -F auid>=1000 -k usb_mount
-w /media -p rwxa -k usb_media_access
-w /mnt   -p rwxa -k usb_mnt_access
AUDIT
systemctl enable auditd && systemctl restart auditd
log "auditd configured"
 
# ── systemd services ──────────────────────────────────────────────────
section "Installing systemd services"
 
install_service() {
    local name="$1" desc="$2" cmd="$3"
    cat > "/etc/systemd/system/$name.service" <<SVC
[Unit]
Description=$desc
After=network.target squid.service
 
[Service]
ExecStart=$cmd
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
WorkingDirectory=$INSTALL_DIR/scripts
 
[Install]
WantedBy=multi-user.target
SVC
    systemctl daemon-reload
    systemctl enable "$name"
    systemctl start  "$name" && log "Service started: $name" \
                             || warn "Service failed: $name"
}
 
install_service "usb-alerter"      "USB Real-Time Alert Monitor" \
    "/usr/bin/python3 $INSTALL_DIR/scripts/usb_alerter.py"
 
install_service "threat-response"  "Automated Threat Response Engine" \
    "/usr/bin/python3 $INSTALL_DIR/scripts/threat_response.py --daemon"
 
install_service "web-request-server" "Web Access Request Server" \
    "/usr/bin/python3 $INSTALL_DIR/scripts/request_server.py"
 
install_service "push-register"    "Push Token Registration Server" \
    "/usr/bin/python3 $INSTALL_DIR/scripts/push_notifier.py --register-server"
 
install_service "admin-ui"         "IT Admin Configuration UI" \
    "/usr/bin/python3 $INSTALL_DIR/scripts/admin_ui.py"
 
# ── crontab ───────────────────────────────────────────────────────────
section "Setting up scheduled tasks"
crontab -l 2>/dev/null | grep -v "security-platform" > /tmp/existing_cron || true
cat >> /tmp/existing_cron <<CRON
# ── Security Platform ─────────────────────────────────────────────────
*/15 * * * * python3 $INSTALL_DIR/scripts/ad_ldap_sync.py     >> $LOG_DIR/ldap.log 2>&1
*/30 * * * * python3 $INSTALL_DIR/scripts/generate_dashboard.py >> $LOG_DIR/dashboard.log 2>&1
*/10 * * * * python3 $INSTALL_DIR/scripts/report_dashboard.py  >> $LOG_DIR/webreport.log 2>&1
0    * * * * python3 $INSTALL_DIR/scripts/risk_engine.py       >> $LOG_DIR/risk.log 2>&1
0    7 * * 1 python3 $INSTALL_DIR/scripts/executive_report.py  >> $LOG_DIR/exec-report.log 2>&1
0    6 * * * python3 $INSTALL_DIR/scripts/dept_policies.py     >> $LOG_DIR/policies.log 2>&1
CRON
crontab /tmp/existing_cron
log "Crontab configured"
 
# ── Firewall ──────────────────────────────────────────────────────────
section "Configuring firewall"
if command -v ufw &>/dev/null; then
    ufw allow 443/tcp   comment "Security Dashboard HTTPS"
    ufw allow "$PROXY_PORT/tcp" comment "Squid Proxy"
    ufw allow 22/tcp    comment "SSH"
    ufw --force enable
    log "ufw configured"
elif command -v firewall-cmd &>/dev/null; then
    firewall-cmd --permanent --add-port=443/tcp
    firewall-cmd --permanent --add-port="$PROXY_PORT/tcp"
    firewall-cmd --permanent --add-service=ssh
    firewall-cmd --reload
    log "firewalld configured"
fi
 
# ── Print credentials ─────────────────────────────────────────────────
section "Installation Complete"
 
ADMIN_PASS=$(grep admin /etc/nginx/usb-auth/admins.htpasswd | cut -d: -f2 || echo "see /etc/nginx/usb-auth/admins.htpasswd")
 
echo ""
echo -e "${GREEN}╔════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║        Security Platform — Ready                   ║${NC}"
echo -e "${GREEN}╚════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "  ${CYAN}Dashboard:${NC}     https://security.$DOMAIN"
echo -e "  ${CYAN}Admin UI:${NC}      https://security.$DOMAIN/admin/"
echo -e "  ${CYAN}Squid Proxy:${NC}   $(hostname -I | awk '{print $1}'):$PROXY_PORT"
echo ""
echo -e "  ${YELLOW}Credentials (change immediately!):${NC}"
echo -e "  Admin user:    admin"
echo -e "  Auditor user:  auditor"
echo -e "  Manager user:  manager"
echo -e "  Passwords:     /etc/nginx/usb-auth/*.htpasswd"
echo ""
echo -e "  ${YELLOW}Next steps:${NC}"
echo -e "  1. Edit $CONFIG_FILE with real values"
echo -e "  2. Set proxy on all clients: $(hostname -I | awk '{print $1}'):$PROXY_PORT"
echo -e "  3. Open Admin UI and configure departments"
echo -e "  4. Add DNS entry: security.$DOMAIN → $(hostname -I | awk '{print $1}')"
echo ""
echo -e "  ${CYAN}Logs:${NC} $LOG_DIR"
echo -e "  ${CYAN}Data:${NC} $DATA_DIR"
echo ""

Part 2: IT Admin Configuration UI

Save as admin_ui.py:

python

#!/usr/bin/env python3
"""
IT Admin Configuration UI
Full web interface for managing all platform settings
Built with Flask — runs on port 8443
"""
 
from flask import (Flask, render_template_string, request,
                   redirect, url_for, flash, jsonify, session)
from flask_login import (LoginManager, UserMixin,
                         login_user, logout_user,
                         login_required, current_user)
import sqlite3, json, os, subprocess, hashlib, secrets
from datetime import datetime
 
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
 
INSTALL_DIR  = "/opt/security-platform"
DATA_DIR     = "/var/lib/security-platform"
ADMIN_DB     = f"{DATA_DIR}/db/admin.db"
POLICY_FILE  = f"{INSTALL_DIR}/scripts/dept_policies.json"
BLOCKED_FILE = "/etc/squid/blocked_domains.txt"
WHITELIST    = "/etc/squid/whitelist.txt"
BLOCKED_USERS= "/etc/squid/blocked_users.txt"
 
os.makedirs(os.path.dirname(ADMIN_DB), exist_ok=True)
 
# ── Auth ──────────────────────────────────────────────────────────────
login_manager = LoginManager(app)
login_manager.login_view = "login_page"
 
class User(UserMixin):
    def __init__(self, uid, username, role):
        self.id = uid; self.username = username; self.role = role
 
def init_admin_db():
    conn = sqlite3.connect(ADMIN_DB)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS admins (
            id       INTEGER PRIMARY KEY,
            username TEXT UNIQUE,
            password TEXT,
            role     TEXT DEFAULT 'admin',
            created  TEXT
        )""")
    conn.execute("""
        CREATE TABLE IF NOT EXISTS audit_log (
            id        INTEGER PRIMARY KEY AUTOINCREMENT,
            admin     TEXT,
            action    TEXT,
            details   TEXT,
            timestamp TEXT
        )""")
    conn.execute("""
        CREATE TABLE IF NOT EXISTS platform_config (
            key   TEXT PRIMARY KEY,
            value TEXT,
            updated TEXT
        )""")
    # Default admin
    pw = hashlib.sha256("admin123".encode()).hexdigest()
    conn.execute("""
        INSERT OR IGNORE INTO admins (username,password,role,created)
        VALUES ('admin',?,'superadmin',?)
    """, (pw, datetime.now().isoformat()))
    conn.commit()
    conn.close()
 
@login_manager.user_loader
def load_user(uid):
    conn = sqlite3.connect(ADMIN_DB)
    row  = conn.execute(
        "SELECT id,username,role FROM admins WHERE id=?", (uid,)
    ).fetchone()
    conn.close()
    return User(*row) if row else None
 
def audit(action, details=""):
    conn = sqlite3.connect(ADMIN_DB)
    conn.execute("""
        INSERT INTO audit_log (admin,action,details,timestamp)
        VALUES (?,?,?,?)
    """, (current_user.username if current_user.is_authenticated
          else "system", action, details, datetime.now().isoformat()))
    conn.commit()
    conn.close()
 
def squid_reload():
    subprocess.run(["squid","-k","reconfigure"],
                   capture_output=True, check=False)
 
# ── Base template ─────────────────────────────────────────────────────
BASE = '''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ title }} — Security Admin</title>
<style>
* { box-sizing:border-box; margin:0; padding:0 }
body { font-family:Segoe UI,Ubuntu,sans-serif;
       background:#0f1117; color:#e0e0e0;
       display:flex; min-height:100vh }
aside { width:220px; background:#1a1d27; padding:20px 0;
        border-right:1px solid #2c2f3e; flex-shrink:0;
        display:flex; flex-direction:column }
.logo { padding:0 20px 20px; border-bottom:1px solid #2c2f3e; margin-bottom:12px }
.logo h2 { color:#e74c3c; font-size:1em }
.logo p  { color:#555; font-size:0.75em; margin-top:3px }
nav a { display:block; padding:10px 20px; color:#888;
        text-decoration:none; font-size:0.88em;
        border-left:3px solid transparent; transition:all 0.15s }
nav a:hover, nav a.active { color:#e0e0e0; background:#2c2f3e;
                             border-left-color:#e74c3c }
nav .section { padding:16px 20px 6px; font-size:0.7em;
               color:#444; text-transform:uppercase; letter-spacing:1px }
.main { flex:1; display:flex; flex-direction:column; overflow:hidden }
header { background:#1a1d27; padding:14px 28px;
         border-bottom:1px solid #2c2f3e;
         display:flex; justify-content:space-between; align-items:center }
header h1 { font-size:1em; color:#aaa }
header .user { font-size:0.82em; color:#555 }
.content { flex:1; padding:28px; overflow-y:auto }
.card { background:#1a1d27; border-radius:10px;
        padding:22px; margin-bottom:20px }
.card h2 { font-size:0.95em; color:#e74c3c; margin-bottom:16px;
           padding-bottom:10px; border-bottom:1px solid #2c2f3e }
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:20px }
.grid3 { display:grid; grid-template-columns:1fr 1fr 1fr; gap:16px }
.stat { background:#2c2f3e; border-radius:8px; padding:16px;
        text-align:center }
.stat h3 { font-size:1.8em; color:#e74c3c; margin:0 }
.stat p  { color:#888; font-size:0.8em; margin-top:4px }
input,select,textarea {
  width:100%; background:#2c2f3e; border:1px solid #3a3d4e;
  border-radius:6px; color:#e0e0e0; padding:9px 12px;
  font-size:0.88em; font-family:inherit; outline:none;
  transition:border 0.15s }
input:focus,select:focus,textarea:focus { border-color:#e74c3c }
textarea { height:120px; resize:vertical }
label { font-size:0.8em; color:#888; display:block; margin-bottom:5px }
.form-group { margin-bottom:14px }
.btn { padding:9px 20px; border-radius:6px; border:none;
       cursor:pointer; font-size:0.88em; font-weight:600;
       transition:opacity 0.2s; text-decoration:none; display:inline-block }
.btn:hover { opacity:0.85 }
.btn-primary  { background:#e74c3c; color:white }
.btn-success  { background:#2ecc71; color:white }
.btn-warning  { background:#f39c12; color:white }
.btn-info     { background:#3498db; color:white }
.btn-danger   { background:#c0392b; color:white }
.btn-sm { padding:5px 12px; font-size:0.8em }
.btn-secondary { background:#2c2f3e; color:#aaa; border:1px solid #3a3d4e }
table { width:100%; border-collapse:collapse; font-size:0.85em }
th { background:#2c2f3e; color:#aaa; padding:10px 12px;
     text-align:left; font-weight:500 }
td { padding:9px 12px; border-bottom:1px solid #1e2130 }
tr:hover td { background:#1e2130 }
.badge { padding:2px 9px; border-radius:10px; font-size:0.78em; font-weight:bold }
.badge-red    { background:#e74c3c; color:white }
.badge-green  { background:#2ecc71; color:white }
.badge-orange { background:#f39c12; color:white }
.badge-blue   { background:#3498db; color:white }
.alert { padding:12px 16px; border-radius:6px; margin-bottom:16px; font-size:0.88em }
.alert-success { background:#1a3a2a; border:1px solid #2ecc71; color:#2ecc71 }
.alert-error   { background:#3a1a1a; border:1px solid #e74c3c; color:#e74c3c }
.toggle { position:relative; display:inline-block; width:44px; height:24px }
.toggle input { opacity:0; width:0; height:0 }
.slider { position:absolute; cursor:pointer; inset:0;
          background:#2c2f3e; border-radius:24px; transition:.3s }
.slider:before { position:absolute; content:""; height:18px; width:18px;
                 left:3px; bottom:3px; background:white;
                 border-radius:50%; transition:.3s }
input:checked + .slider { background:#e74c3c }
input:checked + .slider:before { transform:translateX(20px) }
.tabs { display:flex; border-bottom:1px solid #2c2f3e; margin-bottom:20px }
.tab  { padding:10px 20px; cursor:pointer; font-size:0.88em; color:#888;
        border-bottom:2px solid transparent; transition:all 0.15s }
.tab.active,.tab:hover { color:#e0e0e0; border-bottom-color:#e74c3c }
.tab-panel { display:none }
.tab-panel.active { display:block }
</style>
</head>
<body>
<aside>
  <div class="logo">
    <h2>🛡 Security Admin</h2>
    <p>IT Management Console</p>
  </div>
  <nav>
    <div class="section">Overview</div>
    <a href="/admin/dashboard" class="{{ 'active' if page=='dashboard' else '' }}">
      📊 Dashboard</a>
 
    <div class="section">Web Control</div>
    <a href="/admin/blocklist"  class="{{ 'active' if page=='blocklist' else '' }}">
      🚫 Block List</a>
    <a href="/admin/whitelist"  class="{{ 'active' if page=='whitelist' else '' }}">
       Whitelist</a>
    <a href="/admin/departments" class="{{ 'active' if page=='departments' else '' }}">
      🏢 Departments</a>
    <a href="/admin/requests"   class="{{ 'active' if page=='requests' else '' }}">
      🔓 Access Requests</a>
 
    <div class="section">Users</div>
    <a href="/admin/users"      class="{{ 'active' if page=='users' else '' }}">
      👤 User Policies</a>
    <a href="/admin/blocked-users" class="{{ 'active' if page=='blocked-users' else '' }}">
      🔒 Blocked Users</a>
    <a href="/admin/risk"       class="{{ 'active' if page=='risk' else '' }}">
      ⚠️ Risk Scores</a>
 
    <div class="section">Settings</div>
    <a href="/admin/config"     class="{{ 'active' if page=='config' else '' }}">
      ️ Configuration</a>
    <a href="/admin/alerts"     class="{{ 'active' if page=='alerts' else '' }}">
      🔔 Alert Rules</a>
    <a href="/admin/admins"     class="{{ 'active' if page=='admins' else '' }}">
      👑 Admin Users</a>
    <a href="/admin/auditlog"   class="{{ 'active' if page=='auditlog' else '' }}">
      📋 Audit Log</a>
 
    <div class="section">Actions</div>
    <a href="/admin/services"   class="{{ 'active' if page=='services' else '' }}">
      🔧 Services</a>
    <a href="/admin/logout">🚪 Logout</a>
  </nav>
</aside>
 
<div class="main">
  <header>
    <h1>{{ title }}</h1>
    <div class="user">
      👤 {{ current_user.username }} ({{ current_user.role }}) &nbsp;|&nbsp;
      {{ now }}
    </div>
  </header>
  <div class="content">
    {% with msgs = get_flashed_messages(with_categories=true) %}
      {% for cat,msg in msgs %}
        <div class="alert alert-{{ 'success' if cat=='success' else 'error' }}">
          {{ msg }}
        </div>
      {% endfor %}
    {% endwith %}
    {{ content }}
  </div>
</div>
<script>
function switchTab(tabId) {
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
  document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
  document.getElementById('panel-'+tabId).classList.add('active');
  event.target.classList.add('active');
}
</script>
</body>
</html>'''
 
def render(title, content, page=""):
    return render_template_string(
        BASE, title=title, content=content,
        page=page, now=datetime.now().strftime("%Y-%m-%d %H:%M")
    )
 
# ── Login ─────────────────────────────────────────────────────────────
@app.route("/admin/login", methods=["GET","POST"])
def login_page():
    if request.method == "POST":
        username = request.form.get("username","")
        password = hashlib.sha256(
            request.form.get("password","").encode()
        ).hexdigest()
        conn = sqlite3.connect(ADMIN_DB)
        row  = conn.execute(
            "SELECT id,username,role FROM admins WHERE username=? AND password=?",
            (username, password)
        ).fetchone()
        conn.close()
        if row:
            login_user(User(*row))
            audit("LOGIN", f"Successful login from {request.remote_addr}")
            return redirect(url_for("dashboard"))
        flash("Invalid credentials", "error")
 
    form = '''
    <div style="max-width:380px;margin:80px auto">
      <div class="card">
        <h2 style="text-align:center;margin-bottom:20px">🛡 Admin Login</h2>
        <form method="POST">
          <div class="form-group">
            <label>Username</label>
            <input name="username" placeholder="admin" required autofocus>
          </div>
          <div class="form-group">
            <label>Password</label>
            <input name="password" type="password" placeholder="••••••••" required>
          </div>
          <button class="btn btn-primary" style="width:100%" type="submit">
            Login →
          </button>
        </form>
      </div>
    </div>'''
    return render_template_string(
        BASE.replace("{% if current_user.is_authenticated %}", ""),
        title="Login", content=form, page="login",
        now=datetime.now().strftime("%H:%M")
    )
 
@app.route("/admin/logout")
@login_required
def logout():
    audit("LOGOUT")
    logout_user()
    return redirect(url_for("login_page"))
 
# ── Dashboard ─────────────────────────────────────────────────────────
@app.route("/admin/dashboard")
@app.route("/admin/")
@app.route("/admin")
@login_required
def dashboard():
    # Quick stats
    blocked_count = sum(1 for _ in open(BLOCKED_FILE)
                        if _.strip() and not _.startswith("#")) \
                    if os.path.exists(BLOCKED_FILE) else 0
    white_count   = sum(1 for _ in open(WHITELIST)
                        if _.strip()) \
                    if os.path.exists(WHITELIST) else 0
    blocked_users = sum(1 for _ in open(BLOCKED_USERS_FILE)
                        if _.strip()) \
                    if os.path.exists(BLOCKED_USERS_FILE) else 0
 
    # Service statuses
    services = ["squid","nginx","threat-response",
                "usb-alerter","web-request-server","admin-ui"]
    svc_rows = ""
    for svc in services:
        r = subprocess.run(["systemctl","is-active",svc],
                           capture_output=True, text=True)
        active  = r.stdout.strip() == "active"
        badge   = (f'<span class="badge badge-green">running</span>'
                   if active else
                   f'<span class="badge badge-red">stopped</span>')
        restart = f'<a href="/admin/service/{svc}/restart" class="btn btn-sm btn-warning">↺ Restart</a>'
        svc_rows += f"<tr><td>{svc}</td><td>{badge}</td><td>{restart}</td></tr>"
 
    content = f'''
    <div class="grid3" style="margin-bottom:20px">
      <div class="stat"><h3>{blocked_count}</h3><p>Blocked Domains</p></div>
      <div class="stat"><h3>{white_count}</h3><p>Whitelisted Sites</p></div>
      <div class="stat"><h3>{blocked_users}</h3><p>Blocked Users</p></div>
    </div>
 
    <div class="grid2">
      <div class="card">
        <h2>🔧 Service Status</h2>
        <table>
          <tr><th>Service</th><th>Status</th><th>Action</th></tr>
          {svc_rows}
        </table>
      </div>
      <div class="card">
        <h2> Quick Actions</h2>
        <div style="display:flex;flex-direction:column;gap:10px">
          <a href="/admin/blocklist"    class="btn btn-primary">
             Add Blocked Domain</a>
          <a href="/admin/whitelist"    class="btn btn-success">
             Add Whitelist Exception</a>
          <a href="/admin/blocked-users" class="btn btn-warning">
            🔒 Manage Blocked Users</a>
          <a href="/admin/requests"     class="btn btn-info">
            🔓 Review Access Requests</a>
          <a href="/admin/service/squid/reload" class="btn btn-secondary">
            🔄 Reload Squid Proxy</a>
        </div>
      </div>
    </div>'''
    return render("Dashboard", content, "dashboard")
 
# ── Block List ────────────────────────────────────────────────────────
@app.route("/admin/blocklist", methods=["GET","POST"])
@login_required
def blocklist():
    if request.method == "POST":
        action = request.form.get("action")
        domain = request.form.get("domain","").strip().lower()
        if domain:
            lines = open(BLOCKED_FILE).read().splitlines() \
                    if os.path.exists(BLOCKED_FILE) else []
            if action == "add" and domain not in lines:
                lines.append(domain)
                open(BLOCKED_FILE,"w").write("\n".join(lines)+"\n")
                squid_reload()
                audit("BLOCK_DOMAIN", domain)
                flash(f"Blocked: {domain}", "success")
            elif action == "remove":
                lines = [l for l in lines if l.strip() != domain]
                open(BLOCKED_FILE,"w").write("\n".join(lines)+"\n")
                squid_reload()
                audit("UNBLOCK_DOMAIN", domain)
                flash(f"Removed: {domain}", "success")
 
    domains = []
    if os.path.exists(BLOCKED_FILE):
        domains = [l.strip() for l in open(BLOCKED_FILE)
                   if l.strip() and not l.startswith("#")]
 
    rows = "".join(f"""
      <tr>
        <td>{d}</td>
        <td>
          <form method="POST" style="display:inline">
            <input type="hidden" name="action" value="remove">
            <input type="hidden" name="domain" value="{d}">
            <button class="btn btn-sm btn-danger" type="submit">Remove</button>
          </form>
        </td>
      </tr>""" for d in sorted(domains))
 
    content = f'''
    <div class="grid2">
      <div class="card">
        <h2> Add Blocked Domain</h2>
        <form method="POST">
          <input type="hidden" name="action" value="add">
          <div class="form-group">
            <label>Domain (e.g. gmail.com)</label>
            <input name="domain" placeholder="domain.com" required>
          </div>
          <button class="btn btn-primary" type="submit">Block Domain</button>
        </form>
      </div>
      <div class="card">
        <h2>📋 Blocked Domains ({len(domains)})</h2>
        <input type="text" placeholder="🔍 Filter..."
               onkeyup="filterTable(this)"
               style="margin-bottom:12px">
        <div style="max-height:400px;overflow-y:auto">
          <table id="dt">
            <tr><th>Domain</th><th>Action</th></tr>
            {rows}
          </table>
        </div>
      </div>
    </div>
    <script>
    function filterTable(inp) {{
      document.querySelectorAll('#dt tr:not(:first-child)').forEach(r => {{
        r.style.display = r.innerText.toLowerCase()
          .includes(inp.value.toLowerCase()) ? '' : 'none';
      }});
    }}
    </script>'''
    return render("Block List", content, "blocklist")
 
# ── Whitelist ─────────────────────────────────────────────────────────
@app.route("/admin/whitelist", methods=["GET","POST"])
@login_required
def whitelist_page():
    if request.method == "POST":
        action = request.form.get("action")
        domain = request.form.get("domain","").strip().lower()
        note   = request.form.get("note","")
        if domain:
            lines = open(WHITELIST).read().splitlines() \
                    if os.path.exists(WHITELIST) else []
            if action == "add" and domain not in lines:
                lines.append(domain)
                open(WHITELIST,"w").write("\n".join(lines)+"\n")
                # Also remove from blocked
                if os.path.exists(BLOCKED_FILE):
                    bl = [l for l in open(BLOCKED_FILE).read().splitlines()
                          if l.strip() != domain]
                    open(BLOCKED_FILE,"w").write("\n".join(bl)+"\n")
                squid_reload()
                audit("WHITELIST_ADD", f"{domain} — {note}")
                flash(f"Whitelisted: {domain}", "success")
            elif action == "remove":
                lines = [l for l in lines if l.strip() != domain]
                open(WHITELIST,"w").write("\n".join(lines)+"\n")
                squid_reload()
                audit("WHITELIST_REMOVE", domain)
                flash(f"Removed from whitelist: {domain}", "success")
 
    sites = []
    if os.path.exists(WHITELIST):
        sites = [l.strip() for l in open(WHITELIST)
                 if l.strip() and not l.startswith("#")]
 
    rows = "".join(f"""
      <tr>
        <td style="color:#2ecc71">{s}</td>
        <td>
          <form method="POST" style="display:inline">
            <input type="hidden" name="action" value="remove">
            <input type="hidden" name="domain" value="{s}">
            <button class="btn btn-sm btn-danger">Remove</button>
          </form>
        </td>
      </tr>""" for s in sorted(sites))
 
    content = f'''
    <div class="grid2">
      <div class="card">
        <h2> Add Whitelist Exception</h2>
        <form method="POST">
          <input type="hidden" name="action" value="add">
          <div class="form-group">
            <label>Domain</label>
            <input name="domain" placeholder="allowed-site.com" required>
          </div>
          <div class="form-group">
            <label>Reason / Note</label>
            <input name="note" placeholder="Business requirement...">
          </div>
          <button class="btn btn-success" type="submit">Allow Domain</button>
        </form>
      </div>
      <div class="card">
        <h2>✅ Whitelisted Sites ({len(sites)})</h2>
        <div style="max-height:400px;overflow-y:auto">
          <table>
            <tr><th>Domain</th><th>Action</th></tr>
            {rows}
          </table>
        </div>
      </div>
    </div>'''
    return render("Whitelist", content, "whitelist")
 
# ── Blocked Users ─────────────────────────────────────────────────────
@app.route("/admin/blocked-users", methods=["GET","POST"])
@login_required
def blocked_users_page():
    if request.method == "POST":
        action   = request.form.get("action")
        username = request.form.get("username","").strip()
        if username:
            users = set()
            if os.path.exists(BLOCKED_USERS_FILE):
                users = set(open(BLOCKED_USERS_FILE).read().splitlines())
            if action == "block":
                users.add(username)
                open(BLOCKED_USERS_FILE,"w").write("\n".join(sorted(users))+"\n")
                squid_reload()
                audit("BLOCK_USER", username)
                flash(f"User blocked: {username}", "success")
            elif action == "unblock":
                users.discard(username)
                open(BLOCKED_USERS_FILE,"w").write("\n".join(sorted(users))+"\n")
                squid_reload()
                audit("UNBLOCK_USER", username)
                flash(f"User unblocked: {username}", "success")
 
    users = []
    if os.path.exists(BLOCKED_USERS_FILE):
        users = [u.strip() for u in open(BLOCKED_USERS_FILE)
                 if u.strip()]
 
    rows = "".join(f"""
      <tr>
        <td>👤 {u}</td>
        <td><span class="badge badge-red">BLOCKED</span></td>
        <td>
          <form method="POST" style="display:inline">
            <input type="hidden" name="action"   value="unblock">
            <input type="hidden" name="username" value="{u}">
            <button class="btn btn-sm btn-success">Unblock</button>
          </form>
        </td>
      </tr>""" for u in users)
 
    content = f'''
    <div class="grid2">
      <div class="card">
        <h2>🔒 Block User</h2>
        <form method="POST">
          <input type="hidden" name="action" value="block">
          <div class="form-group">
            <label>Username (AD/LDAP username)</label>
            <input name="username" placeholder="john.doe" required>
          </div>
          <button class="btn btn-danger">Block User</button>
        </form>
      </div>
      <div class="card">
        <h2>🔒 Blocked Users ({len(users)})</h2>
        <table>
          <tr><th>User</th><th>Status</th><th>Action</th></tr>
          {rows or "<tr><td colspan='3' style='color:#555'>No blocked users</td></tr>"}
        </table>
      </div>
    </div>'''
    return render("Blocked Users", content, "blocked-users")
 
# ── Services ──────────────────────────────────────────────────────────
@app.route("/admin/service/<name>/<action>")
@login_required
def service_action(name, action):
    allowed_services = ["squid","nginx","threat-response",
                        "usb-alerter","web-request-server",
                        "push-register","admin-ui","ldap-sync"]
    if name not in allowed_services:
        flash("Unknown service", "error")
        return redirect(url_for("dashboard"))
 
    cmd = {"restart": ["systemctl","restart",name],
           "stop":    ["systemctl","stop",name],
           "start":   ["systemctl","start",name],
           "reload":  ["squid","-k","reconfigure"]
                       if name == "squid"
                       else ["systemctl","reload",name]}
 
    if action in cmd:
        r = subprocess.run(cmd[action], capture_output=True, text=True)
        if r.returncode == 0:
            flash(f"{name} {action}ed successfully", "success")
        else:
            flash(f"Error: {r.stderr.strip()[:100]}", "error")
        audit(f"SERVICE_{action.upper()}", name)
 
    return redirect(url_for("dashboard"))
 
# ── Audit Log ─────────────────────────────────────────────────────────
@app.route("/admin/auditlog")
@login_required
def auditlog():
    conn = sqlite3.connect(ADMIN_DB)
    rows_data = conn.execute("""
        SELECT admin,action,details,timestamp
        FROM audit_log ORDER BY id DESC LIMIT 200
    """).fetchall()
    conn.close()
 
    rows = "".join(f"""<tr>
      <td>{ts[:16]}</td>
      <td>👤 {admin}</td>
      <td><code style="color:#3498db">{action}</code></td>
      <td style="color:#888;font-size:0.82em">{details[:80]}</td>
    </tr>""" for admin,action,details,ts in rows_data)
 
    content = f'''
    <div class="card">
      <h2>📋 Admin Audit Log (Last 200)</h2>
      <table>
        <tr><th>Time</th><th>Admin</th><th>Action</th><th>Details</th></tr>
        {rows or "<tr><td colspan='4' style='color:#555'>No entries</td></tr>"}
      </table>
    </div>'''
    return render("Audit Log", content, "auditlog")
 
# ── Config ────────────────────────────────────────────────────────────
@app.route("/admin/config", methods=["GET","POST"])
@login_required
def config_page():
    CONFIG_KEYS = [
        ("smtp_server",   "SMTP Server",         "smtp.company.com"),
        ("smtp_port",     "SMTP Port",            "587"),
        ("smtp_user",     "SMTP Username",        "it-security@company.com"),
        ("smtp_pass",     "SMTP Password",        ""),
        ("it_email",      "IT Email",             "it-security@company.com"),
        ("soc_email",     "SOC Email",            "soc@company.com"),
        ("slack_webhook", "Slack Webhook URL",    ""),
        ("teams_webhook", "MS Teams Webhook URL", ""),
        ("ad_server",     "AD Server",            "dc01.company.com"),
        ("ldap_server",   "LDAP Server",          "ldap.company.com"),
        ("lan_network",   "LAN Network CIDR",     "192.168.0.0/16"),
        ("exec_emails",   "Executive Email List", "ceo@company.com,cto@company.com"),
    ]
 
    conn = sqlite3.connect(ADMIN_DB)
 
    if request.method == "POST":
        for key,_,_ in CONFIG_KEYS:
            val = request.form.get(key,"")
            conn.execute("""
                INSERT OR REPLACE INTO platform_config (key,value,updated)
                VALUES (?,?,?)
            """, (key, val, datetime.now().isoformat()))
        conn.commit()
        audit("CONFIG_UPDATE", "Platform configuration updated")
        flash("Configuration saved", "success")
 
    config = {}
    for row in conn.execute("SELECT key,value FROM platform_config").fetchall():
        config[row[0]] = row[1]
    conn.close()
 
    fields = "".join(f"""
      <div class="form-group">
        <label>{label}</label>
        <input name="{key}"
               type="{'password' if 'pass' in key or 'key' in key else 'text'}"
               value="{config.get(key, default)}"
               placeholder="{default}">
      </div>"""
        for key,label,default in CONFIG_KEYS)
 
    content = f'''
    <div class="card">
      <h2>⚙️ Platform Configuration</h2>
      <form method="POST">
        <div class="grid2">{fields}</div>
        <button class="btn btn-primary" type="submit">💾 Save Configuration</button>
      </form>
    </div>'''
    return render("Configuration", content, "config")
 
# ── Run ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
    init_admin_db()
    print("[*] Admin UI starting on :8443")
    print("[*] Default login: admin / admin123")
    print("[!] Change password immediately after first login!")
    app.run(host="0.0.0.0", port=8443, debug=False)

Deploy & Run

bash

# 1. Run installer
chmod +x install.sh
sudo bash install.sh        # Creates install.conf
 
# Edit config
nano install.conf            # Fill in your domain, SMTP, AD details
 
# Re-run to apply
sudo bash install.sh
 
# 2. Copy all scripts to install dir
sudo cp *.py /opt/security-platform/scripts/
 
# 3. Start admin UI
sudo systemctl start admin-ui
sudo systemctl status admin-ui
 
# 4. Access
# Admin UI:   https://your-server/admin/
# Dashboard:  https://your-server/
# Default:    admin / admin123

Admin UI Pages

Page

What you can do

Dashboard

Service status, quick actions, stats

Block List

Add/remove blocked domains live

Whitelist

Add exception sites, auto-removes from block list

Departments

Toggle policies per department

Access Requests

Approve/deny bypass requests

User Policies

View AD/LDAP users + their policy

Blocked Users

Block/unblock users from proxy

Risk Scores

View per-user risk scores

Configuration

SMTP, AD, Slack, Teams settings

Alert Rules

Adjust threat thresholds

Admin Users

Manage admin accounts + roles

Audit Log

Full trail of every admin action

Services

Start/stop/restart any service

Part 1: Mobile Companion App (React Native)

Project Structure

SecurityApp/
├── App.js
├── package.json
├── src/
│   ├── api/         client.js
│   ├── screens/     Dashboard, Alerts, Requests, Users, Login
│   ├── components/  AlertCard, StatCard, Badge
│   └── store/       useStore.js

package.json

json

{
  "name": "SecurityCompanion",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "dependencies": {
    "expo": "~50.0.0",
    "expo-notifications": "~0.27.0",
    "expo-device": "~5.9.0",
    "expo-secure-store": "~12.8.0",
    "expo-status-bar": "~1.11.1",
    "@react-navigation/native": "^6.1.0",
    "@react-navigation/bottom-tabs": "^6.5.0",
    "@react-navigation/stack": "^6.3.0",
    "react-native-screens": "~3.29.0",
    "react-native-safe-area-context": "4.8.2",
    "react-native-vector-icons": "^10.0.0",
    "@expo/vector-icons": "^14.0.0",
    "react-native-chart-kit": "^6.12.0",
    "react-native-svg": "14.1.0",
    "axios": "^1.6.0",
    "zustand": "^4.5.0",
    "date-fns": "^3.3.0"
  }
}

src/api/client.js

javascript

// API client — connects to your security platform backend
import axios from 'axios';
import * as SecureStore from 'expo-secure-store';
 
const BASE_URL = 'https://security.company.com';
 
const api = axios.create({
  baseURL: BASE_URL,
  timeout: 10000,
});
 
// Attach auth token to every request
api.interceptors.request.use(async (config) => {
  const token = await SecureStore.getItemAsync('auth_token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});
 
// ── Auth ──────────────────────────────────────────────────────────────
export const login = (username, password) =>
  api.post('/api/auth/login', { username, password });
 
export const logout = () =>
  api.post('/api/auth/logout');
 
// ── Dashboard stats ───────────────────────────────────────────────────
export const getDashboardStats = () =>
  api.get('/api/stats/summary');
 
export const getWebStats = (hours = 24) =>
  api.get(`/api/stats/web?hours=${hours}`);
 
export const getUSBStats = (days = 7) =>
  api.get(`/api/stats/usb?days=${days}`);
 
// ── Alerts / Threats ──────────────────────────────────────────────────
export const getAlerts = (limit = 50) =>
  api.get(`/api/alerts?limit=${limit}`);
 
export const resolveAlert = (id) =>
  api.post(`/api/alerts/${id}/resolve`);
 
export const getThreats = () =>
  api.get('/api/threats/active');
 
// ── Access Requests ───────────────────────────────────────────────────
export const getAccessRequests = (status = 'pending') =>
  api.get(`/api/requests?status=${status}`);
 
export const approveRequest = (refId, notes = '') =>
  api.post(`/api/requests/${refId}/approve`, { notes });
 
export const denyRequest = (refId, notes = '') =>
  api.post(`/api/requests/${refId}/deny`, { notes });
 
// ── Users ─────────────────────────────────────────────────────────────
export const getRiskUsers = () =>
  api.get('/api/users/risk');
 
export const blockUser = (username, reason) =>
  api.post('/api/users/block', { username, reason });
 
export const unblockUser = (username) =>
  api.post(`/api/users/${username}/unblock`);
 
// ── Block/Whitelist ───────────────────────────────────────────────────
export const addBlockedDomain = (domain) =>
  api.post('/api/blocklist/add', { domain });
 
export const addWhitelistDomain = (domain, note) =>
  api.post('/api/whitelist/add', { domain, note });
 
// ── Push token registration ───────────────────────────────────────────
export const registerPushToken = (token, platform) =>
  api.post('/register-push', { token, platform, role: 'soc' });

src/store/useStore.js

javascript

import { create } from 'zustand';
 
const useStore = create((set, get) => ({
  // Auth
  user:        null,
  authToken:   null,
  setUser:     (user)      => set({ user }),
  setToken:    (authToken) => set({ authToken }),
 
  // Data
  stats:       null,
  alerts:      [],
  requests:    [],
  riskUsers:   [],
  threats:     [],
 
  setStats:     (stats)     => set({ stats }),
  setAlerts:    (alerts)    => set({ alerts }),
  setRequests:  (requests)  => set({ requests }),
  setRiskUsers: (riskUsers) => set({ riskUsers }),
  setThreats:   (threats)   => set({ threats }),
 
  // Unread counts
  unreadAlerts:   0,
  pendingRequests: 0,
  setUnreadAlerts:    (n) => set({ unreadAlerts: n }),
  setPendingRequests: (n) => set({ pendingRequests: n }),
}));
 
export default useStore;

src/screens/LoginScreen.js

javascript

import React, { useState } from 'react';
import {
  View, Text, TextInput, TouchableOpacity,
  StyleSheet, ActivityIndicator, Alert, KeyboardAvoidingView
} from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { login } from '../api/client';
import useStore from '../store/useStore';
 
export default function LoginScreen({ navigation }) {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [loading,  setLoading]  = useState(false);
  const { setUser, setToken }   = useStore();
 
  const handleLogin = async () => {
    if (!username || !password) {
      Alert.alert('Error', 'Enter username and password');
      return;
    }
    setLoading(true);
    try {
      const res = await login(username, password);
      await SecureStore.setItemAsync('auth_token', res.data.token);
      await SecureStore.setItemAsync('username',   username);
      setToken(res.data.token);
      setUser({ username, role: res.data.role });
      navigation.replace('Main');
    } catch (e) {
      Alert.alert('Login Failed',
        e.response?.data?.message || 'Invalid credentials');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <KeyboardAvoidingView style={s.container} behavior="padding">
      <View style={s.card}>
        <Text style={s.icon}>🛡</Text>
        <Text style={s.title}>Security Console</Text>
        <Text style={s.sub}>IT Security Operations</Text>
 
        <TextInput
          style={s.input}
          placeholder="Username"
          placeholderTextColor="#555"
          value={username}
          onChangeText={setUsername}
          autoCapitalize="none"
          autoCorrect={false}
        />
        <TextInput
          style={s.input}
          placeholder="Password"
          placeholderTextColor="#555"
          value={password}
          onChangeText={setPassword}
          secureTextEntry
        />
        <TouchableOpacity
          style={s.btn}
          onPress={handleLogin}
          disabled={loading}
        >
          {loading
            ? <ActivityIndicator color="#fff" />
            : <Text style={s.btnText}>Login</Text>
          }
        </TouchableOpacity>
      </View>
    </KeyboardAvoidingView>
  );
}
 
const s = StyleSheet.create({
  container: { flex:1, backgroundColor:'#0f1117',
               justifyContent:'center', padding:24 },
  card:  { backgroundColor:'#1a1d27', borderRadius:16,
           padding:32, alignItems:'center',
           borderTopWidth:3, borderTopColor:'#e74c3c' },
  icon:  { fontSize:48, marginBottom:12 },
  title: { fontSize:22, fontWeight:'bold',
           color:'#e0e0e0', marginBottom:4 },
  sub:   { color:'#555', fontSize:13, marginBottom:28 },
  input: { width:'100%', backgroundColor:'#2c2f3e',
           borderRadius:8, padding:14, color:'#e0e0e0',
           marginBottom:14, fontSize:15,
           borderWidth:1, borderColor:'#3a3d4e' },
  btn:   { width:'100%', backgroundColor:'#e74c3c',
           borderRadius:8, padding:15, alignItems:'center' },
  btnText: { color:'white', fontWeight:'bold', fontSize:16 },
});

src/screens/DashboardScreen.js

javascript

import React, { useEffect, useState, useCallback } from 'react';
import {
  View, Text, ScrollView, StyleSheet,
  RefreshControl, TouchableOpacity, Dimensions
} from 'react-native';
import { LineChart } from 'react-native-chart-kit';
import { getDashboardStats, getWebStats } from '../api/client';
import useStore from '../store/useStore';
 
const W = Dimensions.get('window').width;
 
function StatCard({ value, label, color = '#e74c3c', sub }) {
  return (
    <View style={[s.statCard, { borderTopColor: color }]}>
      <Text style={[s.statVal, { color }]}>{value}</Text>
      <Text style={s.statLabel}>{label}</Text>
      {sub ? <Text style={s.statSub}>{sub}</Text> : null}
    </View>
  );
}
 
export default function DashboardScreen() {
  const [refreshing, setRefreshing] = useState(false);
  const [chartData,  setChartData]  = useState(null);
  const { stats, setStats }         = useStore();
 
  const fetchData = useCallback(async () => {
    try {
      const [statsRes, webRes] = await Promise.all([
        getDashboardStats(),
        getWebStats(24),
      ]);
      setStats(statsRes.data);
 
      // Build chart data from hourly web stats
      const hours = webRes.data.hourly_labels?.slice(-8) || [];
      const total = webRes.data.hourly_total?.slice(-8)  || [];
      const block = webRes.data.hourly_block?.slice(-8)  || [];
      if (hours.length) {
        setChartData({
          labels:   hours.map(h => h.slice(0,5)),
          datasets: [
            { data: total, color: () => '#2ecc71',
              strokeWidth: 2 },
            { data: block, color: () => '#e74c3c',
              strokeWidth: 2 },
          ],
          legend: ['Allowed', 'Blocked'],
        });
      }
    } catch (e) {
      console.error('Dashboard fetch error:', e);
    }
  }, [setStats]);
 
  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    await fetchData();
    setRefreshing(false);
  }, [fetchData]);
 
  useEffect(() => { fetchData(); }, [fetchData]);
 
  return (
    <ScrollView
      style={s.container}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh}
                        tintColor="#e74c3c" />
      }
    >
      <View style={s.header}>
        <Text style={s.headerTitle}>🛡 Security Overview</Text>
        <Text style={s.headerSub}>Last 24 hours</Text>
      </View>
 
      {/* Stat grid */}
      <View style={s.grid}>
        <StatCard
          value={stats?.web_blocked?.toLocaleString() || '—'}
          label="Blocked Requests"
          color="#e74c3c"
          sub={`${stats?.block_rate || 0}% rate`}
        />
        <StatCard
          value={stats?.usb_events?.toLocaleString() || '—'}
          label="USB Events"
          color="#f39c12"
        />
        <StatCard
          value={stats?.threats_today || '—'}
          label="Threats Today"
          color="#9b59b6"
        />
        <StatCard
          value={stats?.pending_requests || '—'}
          label="Pending Requests"
          color="#3498db"
        />
      </View>
 
      {/* Traffic chart */}
      {chartData && (
        <View style={s.chartCard}>
          <Text style={s.sectionTitle}>📈 Traffic (Last 8hrs)</Text>
          <LineChart
            data={chartData}
            width={W - 48}
            height={180}
            chartConfig={{
              backgroundColor:     '#1a1d27',
              backgroundGradientFrom: '#1a1d27',
              backgroundGradientTo:   '#1a1d27',
              decimalPlaces: 0,
              color: (opacity = 1) => `rgba(231,76,60,${opacity})`,
              labelColor: () => '#666',
              propsForDots: { r:'3' },
            }}
            bezier
            style={{ borderRadius: 8 }}
            withLegend
          />
        </View>
      )}
 
      {/* Risk summary */}
      {stats?.critical_users > 0 && (
        <View style={[s.alertBanner, { borderColor:'#e74c3c' }]}>
          <Text style={s.alertBannerText}>
            🔴 {stats.critical_users} users at CRITICAL risk
          </Text>
        </View>
      )}
    </ScrollView>
  );
}
 
const s = StyleSheet.create({
  container:    { flex:1, backgroundColor:'#0f1117' },
  header:       { padding:20, paddingBottom:12 },
  headerTitle:  { fontSize:20, fontWeight:'bold', color:'#e0e0e0' },
  headerSub:    { color:'#555', fontSize:13, marginTop:3 },
  grid:         { flexDirection:'row', flexWrap:'wrap',
                  paddingHorizontal:12, gap:8 },
  statCard:     { width:(W-40)/2, backgroundColor:'#1a1d27',
                  borderRadius:10, padding:16,
                  borderTopWidth:3, margin:4 },
  statVal:      { fontSize:28, fontWeight:'bold' },
  statLabel:    { color:'#888', fontSize:12, marginTop:4 },
  statSub:      { color:'#555', fontSize:11, marginTop:2 },
  chartCard:    { margin:16, backgroundColor:'#1a1d27',
                  borderRadius:12, padding:16 },
  sectionTitle: { color:'#e74c3c', fontSize:13,
                  fontWeight:'bold', marginBottom:12,
                  textTransform:'uppercase', letterSpacing:1 },
  alertBanner:  { margin:16, backgroundColor:'#2a1a1a',
                  borderRadius:8, padding:14,
                  borderWidth:1 },
  alertBannerText: { color:'#e74c3c', fontWeight:'bold' },
});

src/screens/AlertsScreen.js

javascript

import React, { useEffect, useState, useCallback } from 'react';
import {
  View, Text, FlatList, StyleSheet,
  TouchableOpacity, Alert, RefreshControl
} from 'react-native';
import { getAlerts, resolveAlert } from '../api/client';
import useStore from '../store/useStore';
import { formatDistanceToNow } from 'date-fns';
 
const SEVERITY_COLOR = {
  CRITICAL: '#e74c3c', HIGH: '#f39c12',
  MEDIUM:   '#3498db', LOW:  '#2ecc71',
};
 
function AlertCard({ item, onResolve }) {
  const color = SEVERITY_COLOR[item.severity] || '#666';
  return (
    <View style={[s.card, { borderLeftColor: color }]}>
      <View style={s.cardHeader}>
        <View style={[s.badge, { backgroundColor: color }]}>
          <Text style={s.badgeText}>{item.severity}</Text>
        </View>
        <Text style={s.time}>
          {formatDistanceToNow(new Date(item.timestamp), { addSuffix: true })}
        </Text>
      </View>
      <Text style={s.cardTitle}>{item.threat_type}</Text>
      <Text style={s.cardUser}>👤 {item.username}</Text>
      <Text style={s.cardDetails} numberOfLines={2}>
        {item.details}
      </Text>
      {!item.resolved && (
        <TouchableOpacity
          style={s.resolveBtn}
          onPress={() => onResolve(item.id)}
        >
          <Text style={s.resolveBtnText}> Mark Resolved</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}
 
export default function AlertsScreen() {
  const [refreshing, setRefreshing] = useState(false);
  const [filter,     setFilter]     = useState('all');
  const { alerts, setAlerts }       = useStore();
 
  const fetchAlerts = useCallback(async () => {
    try {
      const res = await getAlerts(100);
      setAlerts(res.data);
    } catch (e) { console.error(e); }
  }, [setAlerts]);
 
  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    await fetchAlerts();
    setRefreshing(false);
  }, [fetchAlerts]);
 
  const handleResolve = (id) => {
    Alert.alert('Resolve Alert', 'Mark this alert as resolved?', [
      { text: 'Cancel', style: 'cancel' },
      {
        text: 'Resolve',
        onPress: async () => {
          await resolveAlert(id);
          fetchAlerts();
        }
      }
    ]);
  };
 
  useEffect(() => { fetchAlerts(); }, [fetchAlerts]);
 
  const FILTERS = ['all','CRITICAL','HIGH','MEDIUM'];
  const filtered = filter === 'all'
    ? alerts
    : alerts.filter(a => a.severity === filter);
 
  return (
    <View style={s.container}>
      {/* Filter tabs */}
      <View style={s.filters}>
        {FILTERS.map(f => (
          <TouchableOpacity
            key={f}
            style={[s.filterTab,
                    filter === f && s.filterTabActive]}
            onPress={() => setFilter(f)}
          >
            <Text style={[s.filterText,
                          filter === f && s.filterTextActive]}>
              {f}
            </Text>
          </TouchableOpacity>
        ))}
      </View>
 
      <FlatList
        data={filtered}
        keyExtractor={i => String(i.id)}
        renderItem={({ item }) => (
          <AlertCard item={item} onResolve={handleResolve} />
        )}
        contentContainerStyle={{ padding: 16 }}
        refreshControl={
          <RefreshControl refreshing={refreshing}
                          onRefresh={onRefresh}
                          tintColor="#e74c3c" />
        }
        ListEmptyComponent={
          <Text style={s.empty}>No alerts found</Text>
        }
      />
    </View>
  );
}
 
const s = StyleSheet.create({
  container:   { flex:1, backgroundColor:'#0f1117' },
  filters:     { flexDirection:'row', backgroundColor:'#1a1d27',
                 padding:12, gap:8 },
  filterTab:   { paddingHorizontal:14, paddingVertical:6,
                 borderRadius:16, backgroundColor:'#2c2f3e' },
  filterTabActive: { backgroundColor:'#e74c3c' },
  filterText:  { color:'#888', fontSize:12, fontWeight:'600' },
  filterTextActive: { color:'white' },
  card:        { backgroundColor:'#1a1d27', borderRadius:10,
                 padding:16, marginBottom:12,
                 borderLeftWidth:4 },
  cardHeader:  { flexDirection:'row', justifyContent:'space-between',
                 marginBottom:8 },
  badge:       { paddingHorizontal:10, paddingVertical:3,
                 borderRadius:10 },
  badgeText:   { color:'white', fontSize:10, fontWeight:'bold' },
  time:        { color:'#555', fontSize:11 },
  cardTitle:   { color:'#e0e0e0', fontWeight:'bold',
                 fontSize:14, marginBottom:4 },
  cardUser:    { color:'#3498db', fontSize:12, marginBottom:6 },
  cardDetails: { color:'#666', fontSize:12, lineHeight:18 },
  resolveBtn:  { marginTop:10, backgroundColor:'#2c2f3e',
                 borderRadius:6, padding:8, alignItems:'center' },
  resolveBtnText: { color:'#2ecc71', fontSize:12, fontWeight:'bold' },
  empty:       { color:'#555', textAlign:'center', marginTop:60 },
});

src/screens/RequestsScreen.js

javascript

import React, { useEffect, useState, useCallback } from 'react';
import {
  View, Text, FlatList, StyleSheet,
  TouchableOpacity, Alert, TextInput, RefreshControl
} from 'react-native';
import { getAccessRequests, approveRequest, denyRequest } from '../api/client';
import useStore from '../store/useStore';
 
function RequestCard({ item, onApprove, onDeny }) {
  return (
    <View style={s.card}>
      <View style={s.cardTop}>
        <Text style={s.refId}>{item.ref_id}</Text>
        <View style={[s.badge,
                      { backgroundColor: item.status === 'pending'
                          ? '#f39c12' : item.status === 'approved'
                          ? '#2ecc71' : '#e74c3c' }]}>
          <Text style={s.badgeText}>{item.status?.toUpperCase()}</Text>
        </View>
      </View>
 
      <Text style={s.name}>👤 {item.name}</Text>
      <Text style={s.dept}>🏢 {item.department}</Text>
      <Text style={s.site}>🌐 {item.site}</Text>
      <Text style={s.reason} numberOfLines={2}>{item.reason}</Text>
      <Text style={s.time}>{item.created_at?.slice(0,16)}</Text>
 
      {item.status === 'pending' && (
        <View style={s.actions}>
          <TouchableOpacity
            style={[s.actionBtn, s.approveBtn]}
            onPress={() => onApprove(item.ref_id)}
          >
            <Text style={s.actionBtnText}> Approve</Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[s.actionBtn, s.denyBtn]}
            onPress={() => onDeny(item.ref_id)}
          >
            <Text style={s.actionBtnText}> Deny</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
}
 
export default function RequestsScreen() {
  const [refreshing, setRefreshing] = useState(false);
  const [tab,        setTab]        = useState('pending');
  const { requests, setRequests }   = useStore();
 
  const fetchRequests = useCallback(async () => {
    try {
      const res = await getAccessRequests(tab);
      setRequests(res.data);
    } catch (e) { console.error(e); }
  }, [tab, setRequests]);
 
  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    await fetchRequests();
    setRefreshing(false);
  }, [fetchRequests]);
 
  useEffect(() => { fetchRequests(); }, [fetchRequests]);
 
  const promptAction = (refId, action) => {
    Alert.prompt(
      `${action} Request`,
      'Add a note (optional):',
      async (notes) => {
        try {
          action === 'Approve'
            ? await approveRequest(refId, notes)
            : await denyRequest(refId, notes);
          Alert.alert('Done', `Request ${action.toLowerCase()}d`);
          fetchRequests();
        } catch (e) {
          Alert.alert('Error', 'Action failed');
        }
      },
      'plain-text'
    );
  };
 
  const TABS = ['pending', 'approved', 'denied'];
 
  return (
    <View style={s.container}>
      <View style={s.tabs}>
        {TABS.map(t => (
          <TouchableOpacity
            key={t}
            style={[s.tab, tab === t && s.tabActive]}
            onPress={() => setTab(t)}
          >
            <Text style={[s.tabText, tab === t && s.tabTextActive]}>
              {t.charAt(0).toUpperCase() + t.slice(1)}
            </Text>
          </TouchableOpacity>
        ))}
      </View>
 
      <FlatList
        data={requests}
        keyExtractor={i => i.ref_id}
        renderItem={({ item }) => (
          <RequestCard
            item={item}
            onApprove={(id) => promptAction(id, 'Approve')}
            onDeny={(id)    => promptAction(id, 'Deny')}
          />
        )}
        contentContainerStyle={{ padding:16 }}
        refreshControl={
          <RefreshControl refreshing={refreshing}
                          onRefresh={onRefresh}
                          tintColor="#e74c3c" />
        }
        ListEmptyComponent={
          <Text style={s.empty}>No {tab} requests</Text>
        }
      />
    </View>
  );
}
 
const s = StyleSheet.create({
  container: { flex:1, backgroundColor:'#0f1117' },
  tabs:      { flexDirection:'row', backgroundColor:'#1a1d27',
               borderBottomWidth:1, borderBottomColor:'#2c2f3e' },
  tab:       { flex:1, paddingVertical:14, alignItems:'center',
               borderBottomWidth:2, borderBottomColor:'transparent' },
  tabActive: { borderBottomColor:'#e74c3c' },
  tabText:   { color:'#888', fontSize:13 },
  tabTextActive: { color:'#e0e0e0', fontWeight:'bold' },
  card:      { backgroundColor:'#1a1d27', borderRadius:10,
               padding:16, marginBottom:12 },
  cardTop:   { flexDirection:'row', justifyContent:'space-between',
               marginBottom:10 },
  refId:     { color:'#3498db', fontSize:12,
               fontFamily:'monospace' },
  badge:     { paddingHorizontal:10, paddingVertical:3,
               borderRadius:10 },
  badgeText: { color:'white', fontSize:10, fontWeight:'bold' },
  name:      { color:'#e0e0e0', fontWeight:'bold', fontSize:14,
               marginBottom:4 },
  dept:      { color:'#888', fontSize:12, marginBottom:2 },
  site:      { color:'#e74c3c', fontSize:13,
               fontWeight:'bold', marginBottom:6 },
  reason:    { color:'#666', fontSize:12, marginBottom:6 },
  time:      { color:'#444', fontSize:11 },
  actions:   { flexDirection:'row', gap:10, marginTop:12 },
  actionBtn: { flex:1, padding:10, borderRadius:8,
               alignItems:'center' },
  approveBtn: { backgroundColor:'#1a3a2a' },
  denyBtn:    { backgroundColor:'#3a1a1a' },
  actionBtnText: { fontWeight:'bold', fontSize:13 },
  empty:     { color:'#555', textAlign:'center', marginTop:60 },
});

App.js — Navigation + Push Setup

javascript

import React, { useEffect, useRef } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator } from '@react-navigation/stack';
import { Ionicons } from '@expo/vector-icons';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import * as SecureStore from 'expo-secure-store';
 
import LoginScreen    from './src/screens/LoginScreen';
import DashboardScreen from './src/screens/DashboardScreen';
import AlertsScreen   from './src/screens/AlertsScreen';
import RequestsScreen from './src/screens/RequestsScreen';
import { registerPushToken } from './src/api/client';
import useStore from './src/store/useStore';
 
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge:  true,
  }),
});
 
const Tab   = createBottomTabNavigator();
const Stack = createStackNavigator();
 
const TAB_ICONS = {
  Dashboard: ['speedometer',     'speedometer-outline'],
  Alerts:    ['warning',         'warning-outline'],
  Requests:  ['lock-open',       'lock-open-outline'],
};
 
function MainTabs() {
  const { unreadAlerts, pendingRequests } = useStore();
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        headerShown: false,
        tabBarStyle: { backgroundColor:'#1a1d27',
                       borderTopColor:'#2c2f3e' },
        tabBarActiveTintColor:   '#e74c3c',
        tabBarInactiveTintColor: '#555',
        tabBarIcon: ({ focused, color, size }) => {
          const [filled, outline] = TAB_ICONS[route.name] || ['circle','circle-outline'];
          return (
            <Ionicons
              name={focused ? filled : outline}
              size={size} color={color}
            />
          );
        },
        tabBarBadge: route.name === 'Alerts'   && unreadAlerts   > 0
                       ? unreadAlerts   : undefined,
        tabBarBadge: route.name === 'Requests' && pendingRequests > 0
                       ? pendingRequests : undefined,
      })}
    >
      <Tab.Screen name="Dashboard" component={DashboardScreen} />
      <Tab.Screen name="Alerts"    component={AlertsScreen} />
      <Tab.Screen name="Requests"  component={RequestsScreen} />
    </Tab.Navigator>
  );
}
 
async function registerForPushNotifications() {
  if (!Device.isDevice) return null;
  const { status: existing } = await Notifications.getPermissionsAsync();
  let finalStatus = existing;
  if (existing !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }
  if (finalStatus !== 'granted') return null;
  const token = (await Notifications.getExpoPushTokenAsync()).data;
  const platform = Device.osName === 'Android' ? 'android' : 'ios';
  try {
    await registerPushToken(token, platform);
  } catch (e) { console.error('Push reg error:', e); }
  return token;
}
 
export default function App() {
  const notifListener = useRef();
  const responseListener = useRef();
 
  useEffect(() => {
    registerForPushNotifications();
 
    notifListener.current =
      Notifications.addNotificationReceivedListener(notification => {
        console.log('Push received:', notification);
      });
 
    responseListener.current =
      Notifications.addNotificationResponseReceivedListener(response => {
        const screen = response.notification.request.content.data?.screen;
        // Navigate to relevant screen on tap
        console.log('Push tapped → screen:', screen);
      });
 
    return () => {
      Notifications.removeNotificationSubscription(notifListener.current);
      Notifications.removeNotificationSubscription(responseListener.current);
    };
  }, []);
 
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        <Stack.Screen name="Login" component={LoginScreen} />
        <Stack.Screen name="Main"  component={MainTabs} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}


Part 2: Ansible Playbook — Multi-Server Deployment

Directory Structure

ansible/
├── site.yml                  ← Master playbook
├── inventory/
│   ├── hosts.ini             ← Server inventory
│   └── group_vars/
│       ├── all.yml           ← Global vars
│       ├── proxy_servers.yml ← Proxy-specific vars
│       └── agents.yml        ← Agent vars
├── roles/
│   ├── common/               ← Base setup all servers
│   ├── squid/                ← Squid proxy
│   ├── nginx/                ← nginx + SSL
│   ├── platform/             ← Python services
│   ├── usb_agent/            ← USB monitoring agent
│   └── windows_agent/        ← Windows GPO + scripts
└── files/
    ├── scripts/              ← All Python scripts
    ├── configs/              ← Config templates
    └── certs/                ← SSL certificates

inventory/hosts.ini

ini

# ── Proxy / Central Servers ───────────────────────────────────────────
[proxy_servers]
proxy01 ansible_host=192.168.1.10 ansible_user=root
proxy02 ansible_host=192.168.1.11 ansible_user=root  # HA pair
 
# ── Linux Agents (monitored machines) ────────────────────────────────
[linux_agents]
webserver01  ansible_host=192.168.1.20 ansible_user=root
dbserver01   ansible_host=192.168.1.21 ansible_user=root
devbox01     ansible_host=192.168.1.22 ansible_user=root
devbox02     ansible_host=192.168.1.23 ansible_user=root
 
# ── Windows Agents ────────────────────────────────────────────────────
[windows_agents]
win-ws-01    ansible_host=192.168.1.30
win-ws-02    ansible_host=192.168.1.31
fin-pc-01    ansible_host=192.168.1.32
 
[windows_agents:vars]
ansible_connection=winrm
ansible_winrm_transport=kerberos
ansible_winrm_server_cert_validation=ignore
ansible_user=Administrator
ansible_password="{{ vault_windows_password }}"
 
# ── Groups ────────────────────────────────────────────────────────────
[all_linux:children]
proxy_servers
linux_agents
 
[monitored:children]
linux_agents
windows_agents

inventory/group_vars/all.yml

yaml

---
# ── Company Settings ──────────────────────────────────────────────────
company_name:   "Company Name"
domain:         "company.com"
ad_server:      "dc01.company.com"
ldap_server:    "ldap.company.com"
lan_network:    "192.168.0.0/16"
 
# ── Central Server ────────────────────────────────────────────────────
central_server: "192.168.1.10"
dashboard_url:  "https://security.company.com"
 
# ── Install Paths ─────────────────────────────────────────────────────
install_dir:    "/opt/security-platform"
data_dir:       "/var/lib/security-platform"
log_dir:        "/var/log/security-platform"
web_dir:        "/var/www/html/security"
 
# ── Email ─────────────────────────────────────────────────────────────
smtp_server:    "smtp.company.com"
smtp_port:      587
smtp_user:      "it-security@company.com"
it_email:       "it-security@company.com"
soc_email:      "soc@company.com"
 
# ── Ports ─────────────────────────────────────────────────────────────
proxy_port:     3128
admin_ui_port:  8443
request_port:   8080
push_port:      8081
 
# ── Python packages ───────────────────────────────────────────────────
python_packages:
  - ldap3
  - requests
  - PyJWT
  - cryptography
  - flask
  - flask-login
  - flask-wtf
  - apscheduler
  - paramiko
 
# ── Secrets (use ansible-vault) ───────────────────────────────────────
smtp_pass:       "{{ vault_smtp_pass }}"
slack_webhook:   "{{ vault_slack_webhook }}"
fcm_key:         "{{ vault_fcm_key }}"

roles/common/tasks/main.yml

yaml

---
- name: Detect OS family
  set_fact:
    is_debian: "{{ ansible_os_family == 'Debian' }}"
    is_rhel:   "{{ ansible_os_family == 'RedHat' }}"
 
# ── Debian/Ubuntu ──────────────────────────────────────────────────────
- name: Update apt cache (Debian)
  apt:
    update_cache: yes
    cache_valid_time: 3600
  when: is_debian
 
- name: Install base packages (Debian)
  apt:
    name:
      - python3
      - python3-pip
      - curl
      - wget
      - git
      - openssl
      - rsync
      - sqlite3
      - auditd
      - audispd-plugins
      - inotify-tools
      - ufw
    state: present
  when: is_debian
 
# ── RHEL/CentOS/Fedora ────────────────────────────────────────────────
- name: Install EPEL (RHEL)
  dnf:
    name: epel-release
    state: present
  when: is_rhel
  ignore_errors: yes
 
- name: Install base packages (RHEL)
  dnf:
    name:
      - python3
      - python3-pip
      - curl
      - wget
      - git
      - openssl
      - rsync
      - sqlite
      - audit
      - firewalld
    state: present
  when: is_rhel
 
# ── Common ────────────────────────────────────────────────────────────
- name: Install Python packages
  pip:
    name:         "{{ python_packages }}"
    extra_args:   "--break-system-packages"
    executable:   pip3
  ignore_errors: yes
 
- name: Create platform directories
  file:
    path:  "{{ item }}"
    state: directory
    mode:  '0755'
  loop:
    - "{{ install_dir }}/scripts"
    - "{{ install_dir }}/certs"
    - "{{ data_dir }}/db"
    - "{{ data_dir }}/reports"
    - "{{ log_dir }}"
 
- name: Configure auditd USB rules
  copy:
    dest:    /etc/audit/rules.d/usb.rules
    content: |
      -a always,exit -F arch=b64 -S mount -F auid>=1000 -k usb_mount
      -w /media -p rwxa -k usb_media_access
      -w /mnt   -p rwxa -k usb_mnt_access
  notify: restart auditd
 
- name: Enable and start auditd
  service:
    name:    auditd
    state:   started
    enabled: yes

roles/common/handlers/main.yml

yaml

---
- name: restart auditd
  service:
    name:  auditd
    state: restarted
 
- name: reload squid
  command: squid -k reconfigure
 
- name: restart nginx
  service:
    name:  nginx
    state: restarted

roles/squid/tasks/main.yml

yaml

---
- name: Install Squid
  package:
    name:  "{{ 'squid' if is_debian else 'squid' }}"
    state: present
 
- name: Create Squid department directory
  file:
    path:  /etc/squid/departments
    state: directory
 
- name: Deploy Squid config
  template:
    src:  squid.conf.j2
    dest: /etc/squid/squid.conf
    backup: yes
  notify: reload squid
 
- name: Deploy blocked domains list
  template:
    src:  blocked_domains.j2
    dest: /etc/squid/blocked_domains.txt
  notify: reload squid
 
- name: Create empty ACL files
  file:
    path:  "{{ item }}"
    state: touch
  loop:
    - /etc/squid/whitelist.txt
    - /etc/squid/blocked_users.txt
    - /etc/squid/departments/dept_acls.conf
    - /etc/squid/departments/user_acls.conf
 
- name: Validate Squid config
  command: squid -k parse
  register: squid_parse
  failed_when: squid_parse.rc != 0
 
- name: Enable and start Squid
  service:
    name:    squid
    state:   started
    enabled: yes

roles/squid/templates/squid.conf.j2

jinja2

# Auto-generated by Ansible — {{ ansible_date_time.iso8601 }}
http_port {{ proxy_port }}
acl localnet src {{ lan_network }}
acl SSL_ports  port 443
acl Safe_ports port 80 443 8080 21 70 210 1025-65535
acl CONNECT method CONNECT
 
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
 
acl blocked_users proxy_auth "/etc/squid/blocked_users.txt"
http_access deny blocked_users
 
include /etc/squid/departments/dept_acls.conf
include /etc/squid/departments/user_acls.conf
 
acl whitelist    dstdomain "/etc/squid/whitelist.txt"
acl blocked_sites dstdomain "/etc/squid/blocked_domains.txt"
http_access allow whitelist
http_access deny  blocked_sites
 
http_access allow localnet
http_access deny  all
 
deny_info https://security.{{ domain }}/blocked/?url=%u all
 
access_log /var/log/squid/access.log combined
cache_log  /var/log/squid/cache.log
cache_mem  256 MB

roles/platform/tasks/main.yml

yaml

---
- name: Copy platform scripts
  copy:
    src:   "files/scripts/{{ item }}"
    dest:  "{{ install_dir }}/scripts/{{ item }}"
    mode:  '0755'
  loop:
    - usb_alerter.py
    - ad_ldap_sync.py
    - dept_policies.py
    - threat_response.py
    - executive_report.py
    - push_notifier.py
    - request_server.py
    - risk_engine.py
    - report_dashboard.py
    - generate_dashboard.py
    - admin_ui.py
 
- name: Deploy platform config
  template:
    src:  platform_config.py.j2
    dest: "{{ install_dir }}/scripts/config.py"
 
- name: Install systemd services
  template:
    src:   "service.j2"
    dest:  "/etc/systemd/system/{{ item.name }}.service"
  loop:
    - { name: usb-alerter,
        cmd: "python3 {{ install_dir }}/scripts/usb_alerter.py" }
    - { name: threat-response,
        cmd: "python3 {{ install_dir }}/scripts/threat_response.py --daemon" }
    - { name: web-request-server,
        cmd: "python3 {{ install_dir }}/scripts/request_server.py" }
    - { name: push-register,
        cmd: "python3 {{ install_dir }}/scripts/push_notifier.py --register-server" }
    - { name: admin-ui,
        cmd: "python3 {{ install_dir }}/scripts/admin_ui.py" }
  notify: reload systemd
 
- name: Enable and start platform services
  service:
    name:    "{{ item }}"
    state:   started
    enabled: yes
  loop:
    - usb-alerter
    - threat-response
    - web-request-server
    - push-register
    - admin-ui
 
- name: Install crontab
  cron:
    name:   "{{ item.name }}"
    minute: "{{ item.minute }}"
    hour:   "{{ item.hour | default('*') }}"
    weekday: "{{ item.weekday | default('*') }}"
    job:    "python3 {{ install_dir }}/scripts/{{ item.script }} >> {{ log_dir }}/{{ item.log }} 2>&1"
  loop:
    - { name: ldap-sync,      minute: "*/15", script: ad_ldap_sync.py,      log: ldap.log }
    - { name: dashboard-gen,  minute: "*/30", script: generate_dashboard.py, log: dashboard.log }
    - { name: web-report,     minute: "*/10", script: report_dashboard.py,   log: webreport.log }
    - { name: risk-engine,    minute: "0",    script: risk_engine.py,        log: risk.log }
    - { name: exec-report,    minute: "0",    hour: "7", weekday: "1",
        script: executive_report.py, log: exec-report.log }

roles/usb_agent/tasks/main.yml

yaml

---
- name: Deploy USB audit script
  template:
    src:  usb_audit_report.sh.j2
    dest: "{{ install_dir }}/scripts/usb_audit_report.sh"
    mode: '0755'
 
- name: Deploy USB alerter
  copy:
    src:  files/scripts/usb_alerter.py
    dest: "{{ install_dir }}/scripts/usb_alerter.py"
    mode: '0755'
 
- name: Install USB alerter service
  template:
    src:  service.j2
    dest: /etc/systemd/system/usb-alerter.service
  vars:
    service_name: usb-alerter
    service_cmd:  "python3 {{ install_dir }}/scripts/usb_alerter.py"
  notify: reload systemd
 
- name: Enable USB alerter
  service:
    name:    usb-alerter
    state:   started
    enabled: yes
 
- name: Setup USB report sync to central server
  cron:
    name:   usb-report-sync
    minute: "*/30"
    job: >
      rsync -az --ignore-missing-args
      /var/log/usb-audit-reports/*.csv
      root@{{ central_server }}:{{ data_dir }}/usb-reports/{{ inventory_hostname }}/
      >> {{ log_dir }}/sync.log 2>&1
 
- name: Configure SSH key for central server sync
  authorized_key:
    user:      root
    state:     present
    key:       "{{ lookup('file', 'files/certs/sync_key.pub') }}"

roles/windows_agent/tasks/main.yml

yaml

---
- name: Create scripts directory
  win_file:
    path:  'C:\SecurityAgent\scripts'
    state: directory
 
- name: Deploy USB audit script
  win_copy:
    src:  files/scripts/USB-Audit-Report.ps1
    dest: 'C:\SecurityAgent\scripts\USB-Audit-Report.ps1'
 
- name: Deploy web blocking script
  win_template:
    src:  USB-WebBlock.ps1.j2
    dest: 'C:\SecurityAgent\scripts\USB-WebBlock.ps1'
 
- name: Set proxy system-wide
  win_regedit:
    path:  'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
    name:  "{{ item.name }}"
    data:  "{{ item.data }}"
    type:  "{{ item.type }}"
  loop:
    - { name: ProxyEnable,  data: 1, type: dword }
    - { name: ProxyServer,  data: "{{ central_server }}:{{ proxy_port }}", type: string }
    - { name: ProxyOverride, data: "*.company.com;<local>", type: string }
 
- name: Disable USB storage
  win_regedit:
    path:  'HKLM:\SYSTEM\CurrentControlSet\Services\USBSTOR'
    name:  Start
    data:  4
    type:  dword
 
- name: Schedule USB audit report
  win_scheduled_task:
    name:        USB Audit Report
    description: Weekly USB activity audit
    actions:
      - path:      powershell.exe
        arguments: '-ExecutionPolicy Bypass -File "C:\SecurityAgent\scripts\USB-Audit-Report.ps1"'
    triggers:
      - type:       weekly
        days_of_week: monday
        start_boundary: '2024-01-01T08:00:00'
    run_level:    highest
    state:        present
    enabled:      yes

site.yml — Master Playbook

yaml

---
# ================================================================
# Enterprise Security Platform — Master Ansible Playbook
# Usage:
#   ansible-playbook site.yml -i inventory/hosts.ini
#   ansible-playbook site.yml -i inventory/hosts.ini --tags squid
#   ansible-playbook site.yml -i inventory/hosts.ini --limit proxy01
# ================================================================
 
- name: Common setup — all Linux servers
  hosts: all_linux
  roles:
    - common
  tags: [common]
 
- name: Deploy Squid proxy + platform (proxy servers)
  hosts: proxy_servers
  roles:
    - squid
    - nginx
    - platform
  tags: [proxy, platform]
 
- name: Deploy USB monitoring agents (Linux)
  hosts: linux_agents
  roles:
    - common
    - usb_agent
  tags: [agent, usb]
 
- name: Deploy Windows agents
  hosts: windows_agents
  roles:
    - windows_agent
  tags: [windows]
 
- name: Verify all services running
  hosts: proxy_servers
  tasks:
    - name: Check services
      service_facts:
 
    - name: Assert critical services
      assert:
        that:
          - ansible_facts.services['squid.service'].state == 'running'
          - ansible_facts.services['nginx.service'].state == 'running'
          - ansible_facts.services['threat-response.service'].state == 'running'
        fail_msg: "Critical service not running!"
        success_msg: "All services running ✔"
  tags: [verify]

Run the Playbook

bash

# Install Ansible
pip3 install ansible pywinrm --break-system-packages
 
# Encrypt secrets
ansible-vault encrypt_string 'smtp_password' --name vault_smtp_pass
ansible-vault encrypt_string 'slack_webhook_url' --name vault_slack_webhook
 
# Test connectivity
ansible all -i inventory/hosts.ini -m ping
 
# Dry run
ansible-playbook site.yml -i inventory/hosts.ini --check --diff
 
# Full deploy
ansible-playbook site.yml -i inventory/hosts.ini --ask-vault-pass
 
# Deploy only proxy servers
ansible-playbook site.yml -i inventory/hosts.ini \
    --limit proxy_servers --tags proxy
 
# Deploy only Linux agents
ansible-playbook site.yml -i inventory/hosts.ini \
    --limit linux_agents --tags agent
 
# Deploy only Windows agents
ansible-playbook site.yml -i inventory/hosts.ini \
    --limit windows_agents --tags windows
 
# Update block list across all proxies
ansible proxy_servers -i inventory/hosts.ini \
    -m copy -a "src=files/configs/blocked_domains.txt \
    dest=/etc/squid/blocked_domains.txt" \
    --become && \
ansible proxy_servers -i inventory/hosts.ini \
    -m command -a "squid -k reconfigure"

Complete Platform Summary

┌─────────────────────────────────────────────────────────────────────┐
│              Enterprise Security Platform — Full Stack               │
├──────────────────────┬──────────────────────────────────────────────┤
│   Deployment         │   Components                                 │
│                      │                                              │
│   Ansible ───────────┼──► Proxy Servers (Squid + nginx)            │
│   site.yml           │    Linux Agents  (USB + web)                 │
│   inventory/         │    Windows Agents (GPO + scripts)            │
│                      │                                              │
│   Mobile App ────────┼──► React Native (iOS + Android)             │
│   (Expo)             │    Push Notifications                        │
│                      │    Approve/Deny Requests                     │
│                      │    Live Alerts                               │
│                      │                                              │
│   Admin UI ──────────┼──► Flask Web Console                        │
│   (Browser)          │    Block/Whitelist Management                │
│                      │    User/Policy Control                       │
│                      │    Service Management                        │
│                      │                                              │
│   Reporting ─────────┼──► Live Dashboard                           │
│                      │    Weekly Executive Email                    │
│                      │    Risk Scores                               │
│                      │    Audit Logs                                │
└──────────────────────┴──────────────────────────────────────────────┘
This completes the full enterprise security platform. 





Comments

No Comments have been Posted.

Post Comment

Please Login to Post a Comment.

Ratings

Rating is available to Members only.

Please login or register to vote.

No Ratings have been Posted.
Render time: 3.03 seconds
31,907,073 unique visits