Compare commits

..
1 Commits
Author SHA1 Message Date
MahmoudMo-23 9f773a7b7f Initial commit 2026-07-28 17:30:52 +00:00
19 changed files with 0 additions and 2715 deletions
-108
View File
@@ -1,110 +1,2 @@
# ghaymah-exam-mahmoud-secops # ghaymah-exam-mahmoud-secops
**Candidate:** Mahmoud
**Track:** SecOps
**Platform:** ghaymah.systems
---
## Repository Structure
```
ghaymah-exam-mahmoud-secops/
├── q1-security-audit/
│ ├── security-checklist.md # 15-item checklist (Container, Network, OWASP, Data, IAM)
│ └── security-audit.sh # Bash script: scans ports, SSL, file permissions
├── q2-attack-simulation/
│ ├── incident-response.md # Full Q2: timeline, IR plan, prevention, alert rules
│ └── alert-rule.yml # Deployable Prometheus alert rules for brute force detection
├── q3-privacy-assessment/
│ ├── privacy-report.md # mithal.space assessment: headers, cookies, trackers, comparison
│ └── screenshots/ # Browser screenshots taken during inspection
│ ├── homepage.png
│ ├── https-certificate.png
│ └── search-results.png
├── q4-siem/
│ ├── siem.py # Python log analyzer: auth, nginx, app logs → alerts.json
│ ├── dashboard.html # Near-real-time alert dashboard (dark theme, no dependencies)
│ ├── deploy-guide.md # Deployment on Ghaymah Block Storage via systemd
│ └── alerts.json # Generated alert output (created at runtime)
├── q5-ransomware-response/
│ ├── emergency-plan.md # 60-minute ransomware response playbook
│ ├── backup-recovery-strategy.md # RPO/RTO targets + 3-2-1 rule implementation
│ └── prevention-plan.md # 5-layer prevention: auth, hardening, network, email, monitoring
├── common-mortakaz/
│ ├── Integration-1.md # SIEM ↔ Alerting integration
│ └── Integration-2.md # Backup ↔ Incident Response integration
├── common-qabilah/
│ └── qabilah-profile.txt # Candidate profile
└── README.md
```
---
## Questions Summary
### Q1 — Security Audit for ghaymah.systems
- 15-item security checklist across 5 domains (Container, Network, OWASP, Data, IAM)
- Bash script that automatically scans: open ports, SSL certificate validity, sensitive file permissions
**Run the audit:**
```bash
chmod +x q1-security-audit/security-audit.sh
./q1-security-audit/security-audit.sh ghaymah.systems 443 .
```
---
### Q2 — Attack Simulation & Incident Response
- **Scenario:** Brute force on `/api/v1/auth/login` → successful login → data exfiltration
- All deliverables consolidated in `incident-response.md`:
- Timeline: T+00:00 (recon) → T+01:00 (credential found) → T+01:15 (exfiltration) → T+03:00 (detection)
- 5-phase IR plan: Identification → Containment → Eradication → Recovery → Lessons Learned
- Prevention: rate limiting, WAF, Kubernetes NetworkPolicy, container hardening, MFA
- `alert-rule.yml`: deployable Prometheus rules detecting brute force and anomalous data exports
---
### Q3 — Privacy Assessment of mithal.space
- Inspected via browser DevTools (Network, Application, Security tabs)
- **Findings:** HTTPS enforced ✅, no cookies ✅, no tracking scripts ✅, HSTS missing ❌
- **Privacy score: 8/10 — Strong**
- Compared to DuckDuckGo and Google Search
- 3 recommended improvements: add HSTS, self-host Google Fonts, tighten CSP (remove `'unsafe-inline'`)
---
### Q4 — Lightweight SIEM Log Analyzer
- `siem.py`: reads `auth.log`, `nginx/access.log`, `app/application.log`; detects SSH brute force, HTTP flooding, SQL injection, JWT abuse, and critical application errors
- Outputs `alerts.json` consumed by the dashboard
- `dashboard.html`: near-real-time dark-mode dashboard (30s refresh) with severity stats, IP threat map, activity feed
- Deployment: systemd service + Ghaymah Block Storage for log persistence + backup strategy
**Run locally (demo mode):**
```bash
cd q4-siem
python3 siem.py # Generates alerts.json
# Open dashboard.html in browser
```
---
### Q5 — Ransomware Response Plan
- **Scenario:** All Block Storage files encrypted with ransom message
- Emergency plan: detect → isolate → take forensic snapshot → restore from clean backup → communicate
- Backup strategy: RPO=1h, RTO=4h, 3-2-1 rule (live volume + hourly Object Storage backups + offsite weekly)
- Prevention: 5 layers — access control, system hardening, network segmentation, email filtering, integrity monitoring
---
## How to Use This Repository
| Need | File |
|---|---|
| Run an automated security scan | `q1-security-audit/security-audit.sh` |
| Respond to an active API attack | `q2-attack-simulation/incident-response.md` |
| Set up brute-force alerting | `q2-attack-simulation/alert-rule.yml` |
| Review site privacy posture | `q3-privacy-assessment/privacy-report.md` |
| Deploy a SIEM system | `q4-siem/siem.py` + `q4-siem/deploy-guide.md` |
| Respond to ransomware now | `q5-ransomware-response/emergency-plan.md` |
| Set up backup strategy | `q5-ransomware-response/backup-recovery-strategy.md` |
-51
View File
@@ -1,51 +0,0 @@
# OSS Protector Integration Proposal
## Product Description
OSS Protector is a GitHub security tool designed to detect suspicious pull requests, AI-generated submissions, fake bounty hunting attempts, and potentially malicious code in open-source projects. It helps maintainers identify security risks early and reduce the time spent reviewing unsafe or low-quality contributions.
## Proposed Integration Approach
The OSS Protector service can be deployed on Ghaymah Systems using container hosting. The application can perform automated scans of pull requests and store its security reports and logs in Ghaymah Block Storage.
In addition, scan results and public security reports can be indexed through mithal.space to improve discoverability and provide developers with easy access to security-related information.
The proposed integration includes:
- Hosting the scanning service on Ghaymah Containers.
- Storing reports and logs using Ghaymah Block Storage.
- Making public reports searchable through mithal.space.
## Benefits for End Users
- Faster and more reliable security analysis for open-source projects.
- Scalable hosting infrastructure for automated scans.
- Centralized storage for security reports and logs.
- Improved visibility of public security findings through mithal.space.
- Better developer experience for maintainers and contributors.
## Architecture Sketch
```text
GitHub Repository
|
v
OSS Protector
|
v
Ghaymah Container Hosting
|
v
Ghaymah Block Storage
|
v
mithal.space
```
## Technical or Business Challenges
- Integrating securely with GitHub APIs.
- Managing large volumes of scan results.
- Ensuring data privacy for private repositories.
- Optimizing scan performance for large projects.
- Encouraging adoption among open-source maintainers.
-58
View File
@@ -1,58 +0,0 @@
# Valida Integration Proposal
## Product Description
Valida is a lightweight and standalone PHP validation library that simplifies input validation and eliminates repetitive validation logic in PHP applications. It provides developers with a clean and reusable approach to validating user data.
## Proposed Integration Approach
Valida can benefit from Ghaymah Systems by hosting its official documentation website, interactive playground, and package demonstration environment using container hosting.
Its documentation, tutorials, and code examples can also be indexed by mithal.space to improve discoverability for Arabic-speaking developers searching for PHP validation solutions.
The proposed integration includes:
- Hosting the documentation and demo environment on Ghaymah Containers.
- Storing documentation assets using Ghaymah Block Storage.
- Indexing documentation pages through mithal.space.
## Benefits for End Users
- Faster access to documentation and examples.
- Improved searchability for developers.
- Reliable and scalable hosting for package resources.
- Better visibility within the Arabic developer ecosystem.
- Easier onboarding for new PHP developers.
## Architecture Sketch
```text
PHP Developers
|
v
Valida Library
|
v
Documentation & Demo Environment
|
v
Ghaymah Container Hosting
|
v
Ghaymah Block Storage
|
v
mithal.space
```
## Technical or Business Challenges
- Maintaining up-to-date documentation.
- Supporting future library versions.
- Building and maintaining an interactive demo environment.
- Ensuring efficient indexing of documentation content.
- Increasing community awareness and adoption.
## Which Integration Is More Feasible?
The OSS Protector integration is the more feasible proposal because it directly benefits from Ghaymah's infrastructure services, including container hosting and persistent storage. It also aligns naturally with security-focused workloads and provides practical value for open-source maintainers by improving security monitoring and automated analysis.
-3
View File
@@ -1,3 +0,0 @@
Qabilah Profile:
https://qabilah.com/@mahmoudselim1121
-185
View File
@@ -1,185 +0,0 @@
#!/usr/bin/env bash
# security-audit.sh
# Security audit script for applications hosted on Ghaymah or any VPS/PaaS environment.
# Checks:
# 1. Open ports
# 2. SSL certificate status
# 3. Sensitive file permissions
set -euo pipefail
DOMAIN="${1:-}"
PORT="${2:-443}"
APP_DIR="${3:-.}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() {
echo -e "${GREEN}[OK]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
fail() {
echo -e "${RED}[FAIL]${NC} $1"
}
echo "=================================================="
echo " Automated Security Audit - $(date '+%Y-%m-%d %H:%M:%S')"
echo "=================================================="
# --------------------------------------------------
# 1. Open Ports Check
# --------------------------------------------------
echo
echo "---- Checking Open Ports ----"
if command -v ss >/dev/null 2>&1; then
OPEN_PORTS=$(ss -tuln | awk 'NR>1 {print $5}' | sed -E 's/.*:([0-9]+)$/\1/' | sort -un)
elif command -v netstat >/dev/null 2>&1; then
OPEN_PORTS=$(netstat -tuln | awk 'NR>2 {print $4}' | sed -E 's/.*:([0-9]+)$/\1/' | sort -un)
else
warn "Neither ss nor netstat is available. Skipping port check."
OPEN_PORTS=""
fi
ALLOWED_PORTS="22 80 443"
if [ -n "$OPEN_PORTS" ]; then
echo "Open ports: $OPEN_PORTS"
for port in $OPEN_PORTS; do
if echo "$ALLOWED_PORTS" | grep -qw "$port"; then
pass "Port $port is allowed"
else
warn "Port $port is open and not in the allowed list"
fi
done
else
warn "No ports detected"
fi
# --------------------------------------------------
# 2. SSL Certificate Check
# --------------------------------------------------
echo
echo "---- Checking SSL Certificate ----"
if [ -z "$DOMAIN" ]; then
warn "No domain provided. Skipping SSL check."
else
if command -v openssl >/dev/null 2>&1; then
CERT_INFO=$(echo | timeout 10 openssl s_client \
-connect "${DOMAIN}:${PORT}" \
-servername "$DOMAIN" 2>/dev/null | \
openssl x509 -noout -dates -issuer -subject 2>/dev/null) || true
if [ -z "$CERT_INFO" ]; then
fail "Unable to retrieve SSL certificate"
else
echo "$CERT_INFO"
EXPIRY_DATE=$(echo "$CERT_INFO" | grep "notAfter" | cut -d= -f2)
if [ -n "$EXPIRY_DATE" ]; then
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s 2>/dev/null || echo 0)
CURRENT_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - CURRENT_EPOCH) / 86400 ))
if [ "$DAYS_LEFT" -lt 0 ]; then
fail "SSL certificate expired"
elif [ "$DAYS_LEFT" -lt 30 ]; then
warn "SSL certificate expires in $DAYS_LEFT days"
else
pass "SSL certificate valid for $DAYS_LEFT days"
fi
fi
fi
else
warn "OpenSSL is not installed"
fi
fi
# --------------------------------------------------
# 3. Sensitive File Permissions Check
# --------------------------------------------------
echo
echo "---- Checking Sensitive File Permissions ----"
SENSITIVE_FILES=(
".env"
"*.pem"
"*.key"
"id_rsa"
"config/secrets*"
)
for pattern in "${SENSITIVE_FILES[@]}"; do
while IFS= read -r -d '' file; do
PERMISSIONS=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%A" "$file")
if [ -n "$PERMISSIONS" ]; then
LAST_DIGIT="${PERMISSIONS: -1}"
if [ "$LAST_DIGIT" -ge 4 ] 2>/dev/null; then
fail "$file has insecure permissions ($PERMISSIONS)"
else
pass "$file permissions are secure ($PERMISSIONS)"
fi
fi
done < <(find "$APP_DIR" -type f -iname "$pattern" -print0 2>/dev/null)
done
echo
echo "Checking world-writable files..."
WORLD_WRITABLE=$(find "$APP_DIR" -type f -perm -o+w 2>/dev/null | grep -v "/.git/" || true)
if [ -n "$WORLD_WRITABLE" ]; then
fail "World-writable files found:"
echo "$WORLD_WRITABLE"
else
pass "No world-writable files found."
fi
echo
echo "=================================================="
echo " Security Audit Completed"
echo "=================================================="
-157
View File
@@ -1,157 +0,0 @@
# Security Audit Checklist for Ghaymah Application
## 1. Use Trusted Container Images
### What is the security control?
Using trusted and verified container images reduces the risk of deploying images that contain malware, vulnerabilities, or unwanted software.
### How to apply it on Ghaymah?
Use official images from trusted registries and verify images before deployment on Ghaymah Container Hosting. Enable image scanning to detect known vulnerabilities before running containers.
---
## 2. Scan Container Images for Vulnerabilities
### What is the security control?
Container image scanning identifies vulnerable packages and outdated dependencies before they reach production.
### How to apply it on Ghaymah?
Integrate vulnerability scanning tools into the CI/CD pipeline before pushing images to Ghaymah Container Registry. Block deployment of images with critical vulnerabilities.
---
## 3. Run Containers as Non-Root Users
### What is the security control?
Running containers with root privileges increases the impact of a container compromise.
### How to apply it on Ghaymah?
Configure Docker images to use a dedicated non-root user and enforce security policies that prevent privileged container execution.
---
## 4. Limit Container Resources
### What is the security control?
Resource limits prevent one container from consuming excessive CPU or memory and affecting other services.
### How to apply it on Ghaymah?
Configure CPU and memory limits for containers using Ghaymah Container platform settings. This helps prevent resource exhaustion attacks.
---
## 5. Keep Containers Updated
### What is the security control?
Regular updates fix security vulnerabilities in operating systems, libraries, and application dependencies.
### How to apply it on Ghaymah?
Regularly rebuild container images with updated dependencies and redeploy secure versions through the CI/CD pipeline.
---
# Network Security
## 6. Restrict Exposed Ports
### What is the security control?
Reducing exposed ports minimizes the attack surface available to attackers.
### How to apply it on Ghaymah?
Only expose required application ports and block unnecessary ports using Ghaymah network security rules.
---
## 7. Apply Firewall Rules
### What is the security control?
Firewalls control incoming and outgoing traffic based on defined security rules.
### How to apply it on Ghaymah?
Configure network access rules to allow only trusted sources and required services.
---
## 8. Use Network Policies
### What is the security control?
Network policies restrict communication between different services and containers.
### How to apply it on Ghaymah?
Create rules that control which containers can communicate with each other and prevent unauthorized internal access.
---
## 9. Enable HTTPS/TLS
### What is the security control?
HTTPS encrypts communication between users and applications to protect sensitive information.
### How to apply it on Ghaymah?
Configure SSL/TLS certificates for applications deployed on Ghaymah and enforce HTTPS connections.
---
# OWASP Security
## 10. Prevent SQL Injection
### What is the security control?
SQL injection happens when attackers insert malicious SQL commands through application inputs.
### How to apply it on Ghaymah?
Use prepared statements, input validation, and secure database access configurations for applications hosted on Ghaymah.
---
## 11. Protect Against Cross-Site Scripting (XSS)
### What is the security control?
XSS protection prevents attackers from injecting malicious scripts into web applications.
### How to apply it on Ghaymah?
Implement input sanitization, output encoding, and proper security headers in applications deployed on Ghaymah.
---
## 12. Implement Authentication Controls
### What is the security control?
Strong authentication prevents unauthorized users from accessing application resources.
### How to apply it on Ghaymah?
Use strong password policies, multi-factor authentication, and secure session management.
---
# Data Security
## 13. Encrypt Sensitive Data
### What is the security control?
Encryption protects sensitive information from unauthorized access.
### How to apply it on Ghaymah?
Encrypt stored data using secure storage options and use encrypted connections for data transfer.
---
## 14. Secure Backups
### What is the security control?
Secure backups help recover data after accidental deletion or security incidents.
### How to apply it on Ghaymah?
Store backups securely using Ghaymah storage services and restrict backup access using proper permissions.
---
# IAM Security
## 15. Apply Least Privilege Access
### What is the security control?
Least privilege ensures users only have the permissions required to perform their tasks.
### How to apply it on Ghaymah?
Create role-based access control policies and provide users with only the necessary permissions for managing cloud resources.
-69
View File
@@ -1,69 +0,0 @@
# Prometheus Alert Rules — Brute Force Detection
# Deploy this file as a Prometheus alert rules file and configure AlertManager for notification routing.
# Reference: incident-response.md for full context and runbook.
groups:
- name: security_alerts
rules:
# Rule 1: Rapid failed logins from single IP
- alert: BruteForceLoginAttempt
expr: |
sum(rate(http_requests_total{
path="/api/v1/auth/login",
status=~"40[13]"
}[5m])) by (client_ip) > 10
for: 1m
labels:
severity: critical
team: secops
annotations:
summary: "Brute Force Detected — {{ $labels.client_ip }}"
description: |
IP {{ $labels.client_ip }} has made more than 10 failed login
attempts per minute for 1+ minutes. Block IP and review logs.
runbook_url: "internal-security-runbook"
# Rule 2: Successful login after many failures (credential stuffing)
- alert: SuspiciousLoginAfterFailures
expr: |
(
sum(increase(http_requests_total{
path="/api/v1/auth/login",
status=~"40[13]"
}[10m])) by (client_ip) > 20
)
and
(
sum(increase(http_requests_total{
path="/api/v1/auth/login",
status="200"
}[10m])) by (client_ip) > 0
)
for: 0m
labels:
severity: critical
team: secops
annotations:
summary: "Credential Stuffing Success — {{ $labels.client_ip }}"
description: |
IP {{ $labels.client_ip }} had 20+ failed logins followed by a
successful authentication. Likely brute force or credential stuffing.
runbook_url: "internal-security-runbook"
# Rule 3: Abnormal data export volume
- alert: AbnormalDataExport
expr: |
sum(increase(http_response_bytes_total{
path=~"/api/v1/.*/export"
}[5m])) by (user_id) > 10485760
for: 0m
labels:
severity: warning
team: secops
annotations:
summary: "Large Data Export — User {{ $labels.user_id }}"
description: |
User {{ $labels.user_id }} exported more than 10 MB in 5 minutes.
Verify this is an authorized operation.
runbook_url: "internal-security-runbook"
-248
View File
@@ -1,248 +0,0 @@
# Q2 — Attack Simulation & Incident Response
**Scenario:** A successful brute force attack on the API login endpoint led to a data leak.
---
## Part 1: Attack Timeline
### T+00:00 — Reconnaissance
- Attacker scans the public IP using tools like `nmap` or online port scanners.
- Discovers the exposed API endpoint: `POST /api/v1/auth/login`
- Identifies no rate-limiting or lockout protection via repeated test requests.
### T+00:15 — Credential Acquisition
- Attacker uses previously leaked or weak credentials (e.g., from public breach databases or common password lists).
- Builds a wordlist targeting usernames like `admin`, `deploy`, or known email patterns.
### T+00:30 — Brute Force Attack Begins
- An automated script (e.g., `hydra`, `ffuf`, or custom Python) starts sending POST requests to the login endpoint.
- Sends hundreds of attempts per minute with different username/password combinations.
- No lockout policy is enforced — the server continues responding normally.
### T+01:00 — Successful Authentication
- A valid credential pair is found.
- The attacker receives a valid JWT access token.
- **First point of compromise confirmed.**
### T+01:05 — Session Takeover & Enumeration
- Attacker uses the JWT token to call authenticated API endpoints.
- Discovers `/api/v1/users/export` and `/api/v1/reports/download` via path probing.
### T+01:15 — Data Exfiltration Begins
- Attacker downloads a full CSV dump of user records including: names, emails, hashed passwords, internal metadata.
- No anomaly alert fires on the large data download.
### T+01:45 — Exfiltration Complete
- All accessible data is exfiltrated to the attacker's remote server.
- The token remains valid for the full expiry window — no revocation triggered.
### T+03:00 — Detection (Delayed)
- An administrator notices unusual API traffic in access logs.
- Investigation reveals the brute-force pattern in `/api/v1/auth/login` logs.
### T+03:30 — Incident Confirmed
- Security team confirms unauthorized access and data exfiltration.
- **Incident Response Plan is activated.**
---
### Root Cause Summary
| Root Cause | Detail |
|---|---|
| No rate limiting | API accepted unlimited login attempts |
| No account lockout | No lockout after failed attempt threshold |
| Weak or reused credentials | Password matched a known leaked value |
| No anomaly alerting | No alert fired for burst of failed logins |
| Overly broad API authorization | Export endpoint accessible with any valid token |
| No egress monitoring | Large data download went undetected |
---
## Part 2: Incident Response Plan
**Classification:** P1 — Critical Security Incident
**System Affected:** API Login Endpoint
### Phase 1: Identification (0–15 min)
1. Pull API access logs and filter for `POST /api/v1/auth/login` in the last 6 hours.
2. Identify source IPs with >50 failed attempts followed by a successful login.
3. Confirm large API calls to export endpoints post-login.
4. Assign an incident commander and open a dedicated communication channel.
### Phase 2: Containment (15–60 min)
| Action | Method |
|---|---|
| Block attacker IP(s) | Firewall / WAF deny rule |
| Revoke all active JWT tokens | Rotate JWT signing secret or blacklist tokens |
| Force logout all active sessions | Flush session store (Redis/DB) |
| Disable the export endpoint | Feature flag or reverse proxy block |
| Enable emergency rate limiting | Nginx `limit_req_zone` or API gateway rule |
| Enable account lockout | Application config: 5 attempts → 15 min lock |
### Phase 3: Eradication (1–2 hours)
1. Patch the login endpoint with rate limiting, account lockout, and MFA enforcement.
2. Audit all API endpoints for over-permissive authorization.
3. Check for any persistence (e.g., new admin accounts created during the attack).
4. Rotate all application secrets and API keys.
5. Require password reset for all users whose data was exported.
### Phase 4: Recovery (2–8 hours)
1. Re-enable the export endpoint with admin-only authorization.
2. Deploy the patched container image.
3. Verify monitoring and alerting is active.
4. Notify affected users per data breach disclosure requirements.
### Phase 5: Lessons Learned (within 72 hours)
1. Conduct a post-mortem with all stakeholders.
2. Update the threat model and security runbooks.
3. Schedule a penetration test to verify fixes.
---
## Part 3: Prevention on the Cloud
> The following controls can be implemented in a Ghaymah cloud deployment using its networking, container, and monitoring capabilities.
### Network-Level Controls
**Rate Limiting via Nginx:**
```nginx
http {
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
server {
location /api/v1/auth/login {
limit_req zone=login_limit burst=10 nodelay;
limit_req_status 429;
proxy_pass http://app_backend;
}
}
}
```
**Web Application Firewall:**
- Deploy a WAF in front of the application layer.
- Configure rules to block IPs generating >100 requests/min to `/auth` endpoints.
- Block known malicious user agents (e.g., scanner fingerprints).
**IP Restriction for Admin Endpoints:**
```bash
# Allow only internal network to access sensitive endpoints
iptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j DROP
```
**Container Network Policy:**
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-login-access
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 8080
policyTypes:
- Ingress
```
### Container Security Controls
**Run as non-root user:**
```dockerfile
FROM python:3.12-slim
RUN useradd --create-home appuser
USER appuser
```
**Read-only filesystem:**
```yaml
services:
api:
read_only: true
tmpfs:
- /tmp
```
**Secrets via environment injection (never baked into images):**
```yaml
services:
api:
env_file:
- .env.production
```
**Image vulnerability scanning in CI/CD:**
```yaml
- name: Scan Docker image
uses: aquasecurity/trivy-action@master
with:
image-ref: 'registry/api:latest'
exit-code: '1'
severity: 'CRITICAL,HIGH'
```
### Application-Level Controls
| Control | Implementation |
|---|---|
| Account lockout | Lock after 5 failed attempts, notify user by email |
| MFA | TOTP required for all admin accounts |
| Password validation | Reject known-weak passwords, enforce minimum 12 chars |
| JWT expiry | Short-lived access tokens (15 min), refresh token rotation |
| Structured auth logging | Log all login attempts to SIEM for real-time analysis |
---
## Part 4: Brute Force Alert Rule
The full deployable Prometheus rule file is in [`alert-rule.yml`](./alert-rule.yml). It defines three rules:
### Alert Rules Explanation
| Rule | Trigger | Severity | Recommended Action |
|---|---|---|---|
| `BruteForceLoginAttempt` | >10 failed logins/min from same IP for 1+ min | Critical | Auto-block IP at firewall |
| `SuspiciousLoginAfterFailures` | 20+ failures then 1 success from same IP | Critical | Revoke token, notify security team |
| `AbnormalDataExport` | >10 MB exported in 5 min by single user | Warning | Flag for human review |
### AlertManager Notification Config
```yaml
# alertmanager.yml
receivers:
- name: secops-slack
slack_configs:
- api_url: ${SLACK_WEBHOOK_URL}
channel: '#security-alerts'
title: '{{ .CommonAnnotations.summary }}'
text: '{{ .CommonAnnotations.description }}'
- name: secops-pagerduty
pagerduty_configs:
- routing_key: ${PAGERDUTY_KEY}
severity: critical
route:
receiver: secops-slack
routes:
- match:
severity: critical
receiver: secops-pagerduty
```
-138
View File
@@ -1,138 +0,0 @@
# Privacy Assessment Report — mithal.space
> mithal.space is a privacy-focused search engine built on SearXNG, hosted on Ghaymah infrastructure.
---
## 1. HTTPS & Certificate
| Check | Result |
|---|---|
| HTTPS enforced | ✅ Yes — HTTP redirects to HTTPS |
| TLS Version | TLS 1.2 / 1.3 |
| Certificate Issuer | Let's Encrypt (intermediate: `YR1`) |
| Certificate Valid | ✅ Yes (valid 2026-06-17 → 2026-09-15) |
| HSTS Header | ❌ Not observed — missing `Strict-Transport-Security` |
**Finding:** The site correctly enforces HTTPS with a valid Let's Encrypt certificate. The absence of HSTS means browsers will not remember to always use HTTPS on the first visit, leaving a narrow window for SSL-stripping attacks.
**Screenshot:** `screenshots/https-certificate.png`
---
## 2. HTTP Security Headers
Inspected via DevTools → Network → Response Headers on the main document request:
| Header | Present | Observed Value |
|---|---|---|
| `Content-Security-Policy` | ✅ Yes | `default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https: http:; connect-src 'self' [ghaymah internal services]; frame-ancestors 'none';` |
| `X-Frame-Options` | ✅ Yes | `DENY` |
| `X-Content-Type-Options` | ✅ Yes | `nosniff` |
| `X-XSS-Protection` | ✅ Yes | `1; mode=block` |
| `Referrer-Policy` | ✅ Yes | `strict-origin-when-cross-origin` |
| `Permissions-Policy` | ✅ Yes | `geolocation=(), microphone=(), camera=()` |
| `Strict-Transport-Security` | ❌ Missing | Not present in any observed response |
**Finding:** Security headers are well-configured overall. The CSP policy allows `'unsafe-inline'` for scripts and styles (necessary for SearXNG), which slightly weakens XSS protection. HSTS is the main missing control.
---
## 3. Cookies
Inspected via DevTools → Application → Cookies → `https://mithal.space`:
**No cookies were observed.**
The site sets no tracking, session, or analytics cookies. This is consistent with a privacy-focused search engine — user state is not persisted between sessions.
---
## 4. Third-Party Network Requests
Inspected via DevTools → Network tab, filtering for third-party domains:
| Domain | Purpose | Privacy Risk |
|---|---|---|
| `fonts.googleapis.com` | Google Fonts CSS | Low — IP address may be logged by Google |
| `fonts.gstatic.com` | Google Fonts files | Low — same as above |
| `t0/t1/t2.gstatic.com` | Search result favicons | Low — loaded only for displayed results |
| `img.youtube.com` | Video thumbnails in results | Low — loaded only when results include videos |
**Finding:** No behavioral tracking services (e.g., Google Analytics, Facebook Pixel, or similar) were detected. The only third-party connections are to Google for web fonts and result thumbnails — both are functional rather than tracking in nature. However, Google Fonts requests do expose the user's IP address to Google servers.
---
## 5. Privacy Risk Summary
| Category | Score | Notes |
|---|---|---|
| HTTPS & Transport Security | Good | Certificate valid; HSTS missing |
| Cookie Privacy | Excellent | No cookies at all |
| Tracker Exposure | Excellent | No analytics or tracking scripts |
| Security Headers | Good | CSP, X-Frame-Options, Permissions-Policy all present |
| Third-Party Exposure | Low Risk | Google Fonts exposes IP to Google |
Based on the performed checks, mithal.space does not appear to use tracking cookies or analytics services. The site has several security headers enabled, with HSTS being the main missing header.
---
## 6. Comparison to Two Other Sites
| Feature | mithal.space | DuckDuckGo (duckduckgo.com) | Google Search (google.com) |
|---|---|---|---|
| HTTPS | ✅ Yes | ✅ Yes | ✅ Yes |
| Third-Party Trackers | None detected | None | Uses Google-owned services and cookies that may be associated with personalization and advertising features |
| Cookies | ❌ None | Minimal (theme/settings only) | Multiple Google ecosystem cookies |
| Content-Security-Policy | ✅ Present | ✅ Strict | ✅ Present |
| Permissions-Policy | ✅ Present | ✅ Present | ✅ Present |
| HSTS | ❌ Missing | ✅ Full with preload | ✅ Full with preload |
| Data Sharing with 3rd Parties | Google Fonts (IP only) | None | Data collected may be used for personalization and targeting |
| Open Source | ✅ Yes (SearXNG) | ❌ No | ❌ No |
**Summary:** `mithal.space` is significantly more privacy-respecting than Google Search and comparable to DuckDuckGo. Its advantage over DuckDuckGo is being open-source and self-hosted on Ghaymah infrastructure. Its disadvantage is the missing HSTS header and Google Fonts dependency.
---
## 7. Three Security Improvements
### Improvement 1: Add HSTS Header
**Problem:** Without `Strict-Transport-Security`, browsers do not remember to always use HTTPS on the first visit, leaving a small window for SSL downgrade attacks.
**Solution:**
```
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
```
Add to the Nginx or application server response headers. Then submit the domain to the [HSTS preload list](https://hstspreload.org).
**Benefit:** Forces all future connections to use HTTPS from the first visit.
### Improvement 2: Self-Host Web Fonts
**Problem:** `fonts.googleapis.com` and `fonts.gstatic.com` requests expose the user's IP address to Google, even if no tracking cookies are set.
**Solution:** Download the required fonts and serve them directly from `mithal.space`:
```nginx
location /fonts/ {
alias /var/www/fonts/;
add_header Cache-Control "public, max-age=31536000";
}
```
Update the CSP to remove `googleapis.com` and `gstatic.com` from allowed sources.
**Benefit:** Eliminates all third-party IP exposure to Google.
### Improvement 3: Tighten CSP to Remove `'unsafe-inline'`
**Problem:** The current CSP allows `'unsafe-inline'` for scripts and styles. This weakens protection against XSS attacks.
**Solution:** Replace inline styles/scripts with external files and use CSP nonces for any remaining inline code:
```
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self'; ...
```
**Benefit:** Significantly reduces the attack surface for cross-site scripting.
---
## Screenshots
![mithal.space homepage](screenshots/homepage.png)
![SSL certificate verified via crt.sh](screenshots/https-certificate.png)
![Search results page](screenshots/search-results.png)
> Additional DevTools screenshots (Cookies panel empty, Response Headers, Network requests) can be captured manually in Chrome/Firefox via F12 and saved to the `screenshots/` folder.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

-33
View File
@@ -1,33 +0,0 @@
{
"generated_at": "2026-07-26T22:30:54.950560",
"total_alerts": 2,
"critical_count": 0,
"high_count": 0,
"medium_count": 2,
"alerts": [
{
"id": 1,
"timestamp": "2026-07-26T22:30:54.950367",
"last_seen": "2026-07-26T22:30:54.950548",
"source": "app/application.log",
"type": "application_error",
"severity": "MEDIUM",
"ip": "unknown",
"detail": "Database connection timeout after 30s \u2014 retrying",
"count": 3,
"status": "open"
},
{
"id": 2,
"timestamp": "2026-07-26T22:30:54.950462",
"last_seen": "2026-07-26T22:30:54.950465",
"source": "app/application.log",
"type": "application_error",
"severity": "MEDIUM",
"ip": "185.220.100.42",
"detail": "JWT verification failed for token eyJ0eXAi... from IP 185.220.100.42",
"count": 1,
"status": "open"
}
]
}
-579
View File
@@ -1,579 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ghaymah — Lightweight SIEM Log Analyzer</title>
<style>
/* System font stack — no external font requests for privacy consistency */
:root {
--bg-base: #0a0e1a;
--bg-card: #111827;
--bg-card2: #1a2235;
--border: #1e2d45;
--accent: #3b82f6;
--critical: #ef4444;
--high: #f97316;
--medium: #eab308;
--low: #22c55e;
--text: #e2e8f0;
--text-muted: #64748b;
--glow-red: 0 0 20px rgba(239,68,68,0.3);
--glow-blue: 0 0 20px rgba(59,130,246,0.2);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
background: var(--bg-base);
color: var(--text);
min-height: 100vh;
}
/* ── Header ── */
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.2rem 2rem;
background: var(--bg-card);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 100;
}
.logo {
display: flex;
align-items: center;
gap: .75rem;
font-size: 1.25rem;
font-weight: 700;
letter-spacing: -.02em;
}
.logo-icon {
width: 36px; height: 36px;
background: linear-gradient(135deg, var(--accent), #6366f1);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 1.1rem;
}
.status-bar {
display: flex;
align-items: center;
gap: 1rem;
font-size: .85rem;
color: var(--text-muted);
}
.status-dot {
width: 8px; height: 8px;
border-radius: 50%;
background: #22c55e;
animation: pulse 2s infinite;
display: inline-block;
margin-right: 4px;
}
@keyframes pulse {
0%,100% { opacity: 1; }
50% { opacity: .4; }
}
#last-updated { font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace; }
/* ── Main Layout ── */
main { padding: 2rem; max-width: 1400px; margin: 0 auto; }
/* ── Stat Cards ── */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.4rem 1.6rem;
position: relative;
overflow: hidden;
transition: transform .2s, box-shadow .2s;
}
.stat-card:hover { transform: translateY(-2px); box-shadow: var(--glow-blue); }
.stat-card::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(59,130,246,.05), transparent);
pointer-events: none;
}
.stat-card.critical::before { background: linear-gradient(135deg, rgba(239,68,68,.08), transparent); }
.stat-card.high::before { background: linear-gradient(135deg, rgba(249,115,22,.08), transparent); }
.stat-card.medium::before { background: linear-gradient(135deg, rgba(234,179,8,.08), transparent); }
.stat-label { font-size: .8rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; margin-bottom: .4rem; }
.stat-value { font-size: 2.4rem; font-weight: 700; line-height: 1; }
.stat-value.critical { color: var(--critical); text-shadow: var(--glow-red); }
.stat-value.high { color: var(--high); }
.stat-value.medium { color: var(--medium); }
.stat-value.ok { color: var(--low); }
.stat-sub { font-size: .75rem; color: var(--text-muted); margin-top: .4rem; }
/* ── Section Title ── */
.section-title {
font-size: .9rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--text-muted);
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: .5rem;
}
/* ── Alert Table ── */
.alerts-panel {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
margin-bottom: 2rem;
}
.alerts-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.2rem 1.6rem;
border-bottom: 1px solid var(--border);
}
.filter-group { display: flex; gap: .5rem; }
.filter-btn {
padding: .35rem .85rem;
border-radius: 6px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-muted);
font-size: .8rem;
cursor: pointer;
transition: all .2s;
font-family: inherit;
}
.filter-btn:hover, .filter-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
table { width: 100%; border-collapse: collapse; }
thead th {
padding: .9rem 1.4rem;
text-align: left;
font-size: .78rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .06em;
color: var(--text-muted);
background: var(--bg-card2);
border-bottom: 1px solid var(--border);
}
tbody tr {
border-bottom: 1px solid rgba(30,45,69,.5);
transition: background .15s;
}
tbody tr:hover { background: var(--bg-card2); }
tbody tr:last-child { border-bottom: none; }
td {
padding: 1rem 1.4rem;
font-size: .88rem;
vertical-align: top;
}
.badge {
display: inline-flex;
align-items: center;
padding: .2rem .7rem;
border-radius: 20px;
font-size: .75rem;
font-weight: 600;
letter-spacing: .03em;
}
.badge-CRITICAL { background: rgba(239,68,68,.15); color: #f87171; }
.badge-HIGH { background: rgba(249,115,22,.15); color: #fb923c; }
.badge-MEDIUM { background: rgba(234,179,8,.15); color: #facc15; }
.badge-LOW { background: rgba(34,197,94,.15); color: #4ade80; }
.type-tag {
font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace;
font-size: .78rem;
color: var(--accent);
background: rgba(59,130,246,.1);
padding: .15rem .5rem;
border-radius: 4px;
}
.ip-text {
font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace;
font-size: .83rem;
color: #94a3b8;
}
.count-badge {
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: 4px;
padding: .1rem .45rem;
font-size: .78rem;
font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace;
}
.detail-text {
color: var(--text-muted);
font-size: .83rem;
max-width: 340px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ts-text {
font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace;
font-size: .78rem;
color: var(--text-muted);
}
/* ── IP Breakdown ── */
.bottom-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
@media (max-width: 900px) { .bottom-grid { grid-template-columns: 1fr; } }
.panel {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.4rem 1.6rem;
}
.ip-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: .6rem 0;
border-bottom: 1px solid rgba(30,45,69,.5);
}
.ip-row:last-child { border-bottom: none; }
.ip-info { display: flex; align-items: center; gap: .75rem; }
.threat-dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.alert-count-bar {
display: flex;
align-items: center;
gap: .5rem;
font-size: .82rem;
color: var(--text-muted);
}
.bar-track {
width: 80px;
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
}
.bar-fill { height: 100%; border-radius: 2px; background: var(--accent); }
/* ── Activity Log ── */
.log-entry {
display: flex;
gap: .75rem;
padding: .6rem 0;
border-bottom: 1px solid rgba(30,45,69,.4);
font-size: .83rem;
}
.log-entry:last-child { border-bottom: none; }
.log-time { font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace; color: var(--text-muted); flex-shrink: 0; }
.log-msg { color: var(--text); }
.log-src { color: var(--accent); font-size: .78rem; margin-left: auto; flex-shrink: 0; }
/* ── No alerts ── */
.empty-state {
padding: 3rem;
text-align: center;
color: var(--text-muted);
font-size: .9rem;
}
/* ── Refresh button ── */
#refresh-btn {
background: var(--accent);
color: #fff;
border: none;
padding: .45rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: .82rem;
font-family: inherit;
transition: opacity .2s;
}
#refresh-btn:hover { opacity: .85; }
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--bg-base); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
</style>
</head>
<body>
<header>
<div class="logo">
<div class="logo-icon">🛡</div>
Ghaymah — Log Analyzer
</div>
<div class="status-bar">
<span><span class="status-dot"></span>Near Real-Time (30s)</span>
<span>Last scan: <span id="last-updated">—</span></span>
<button id="refresh-btn" onclick="loadAlerts()">↻ Refresh</button>
</div>
</header>
<main>
<!-- Stats -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Total Alerts</div>
<div class="stat-value ok" id="stat-total">0</div>
<div class="stat-sub">Active threats</div>
</div>
<div class="stat-card critical">
<div class="stat-label">Critical</div>
<div class="stat-value critical" id="stat-critical">0</div>
<div class="stat-sub">Immediate action required</div>
</div>
<div class="stat-card high">
<div class="stat-label">High</div>
<div class="stat-value high" id="stat-high">0</div>
<div class="stat-sub">Investigate soon</div>
</div>
<div class="stat-card medium">
<div class="stat-label">Medium</div>
<div class="stat-value medium" id="stat-medium">0</div>
<div class="stat-sub">Monitor closely</div>
</div>
<div class="stat-card">
<div class="stat-label">Unique IPs</div>
<div class="stat-value ok" id="stat-ips">0</div>
<div class="stat-sub">Suspicious sources</div>
</div>
</div>
<!-- Alerts Table -->
<div class="alerts-panel">
<div class="alerts-header">
<div class="section-title" style="margin:0">⚡ Security Alerts</div>
<div class="filter-group">
<button class="filter-btn active" onclick="filterAlerts('all', this)">All</button>
<button class="filter-btn" onclick="filterAlerts('CRITICAL', this)">Critical</button>
<button class="filter-btn" onclick="filterAlerts('HIGH', this)">High</button>
<button class="filter-btn" onclick="filterAlerts('MEDIUM', this)">Medium</button>
</div>
</div>
<table>
<thead>
<tr>
<th>#</th>
<th>Severity</th>
<th>Type</th>
<th>Source IP</th>
<th>Detail</th>
<th>Count</th>
<th>Last Seen</th>
</tr>
</thead>
<tbody id="alerts-tbody"></tbody>
</table>
<div id="no-alerts" class="empty-state" style="display:none">
✅ No alerts at this time
</div>
</div>
<!-- Bottom Grid -->
<div class="bottom-grid">
<!-- Top Threat IPs -->
<div class="panel">
<div class="section-title">🌐 Top Threat Sources</div>
<div id="ip-list"></div>
</div>
<!-- Activity Log -->
<div class="panel">
<div class="section-title">📋 Recent Activity</div>
<div id="activity-log"></div>
</div>
</div>
</main>
<script>
// ── Sample data used when alerts.json not available (demo mode) ──
const DEMO_DATA = {
generated_at: new Date().toISOString(),
total_alerts: 5,
critical_count: 2,
high_count: 2,
medium_count: 1,
alerts: [
{ id:1, timestamp: new Date(Date.now()-600000).toISOString(), last_seen: new Date(Date.now()-60000).toISOString(),
source:"auth.log", type:"brute_force_ssh", severity:"CRITICAL", ip:"203.0.113.42",
detail:"6 failed SSH login attempts detected", count:6, status:"open" },
{ id:2, timestamp: new Date(Date.now()-540000).toISOString(), last_seen: new Date(Date.now()-30000).toISOString(),
source:"nginx/access.log", type:"sql_injection_attempt", severity:"HIGH", ip:"203.0.113.42",
detail:"Possible SQL injection detected in request path: /api/v1/users?id=1 UNION SELECT * FROM users--", count:1, status:"open" },
{ id:3, timestamp: new Date(Date.now()-480000).toISOString(), last_seen: new Date(Date.now()-480000).toISOString(),
source:"nginx/access.log", type:"repeated_http_errors", severity:"HIGH", ip:"203.0.113.42",
detail:"3 HTTP error responses to IP in log window", count:3, status:"open" },
{ id:4, timestamp: new Date(Date.now()-300000).toISOString(), last_seen: new Date(Date.now()-300000).toISOString(),
source:"app/application.log", type:"application_error", severity:"CRITICAL", ip:"203.0.113.42",
detail:"JWT verification failed for token eyJ0eXAi... from IP 203.0.113.42", count:1, status:"open" },
{ id:5, timestamp: new Date(Date.now()-200000).toISOString(), last_seen: new Date(Date.now()-200000).toISOString(),
source:"app/application.log", type:"application_error", severity:"MEDIUM", ip:"unknown",
detail:"Unhandled exception in /api/v1/users/export: PermissionError", count:1, status:"open" },
]
};
let allAlerts = [];
let currentFilter = 'all';
function formatTime(iso) {
const d = new Date(iso);
return d.toLocaleTimeString('en-GB', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
}
function timeSince(iso) {
const sec = Math.floor((Date.now() - new Date(iso)) / 1000);
if (sec < 60) return `${sec}s ago`;
if (sec < 3600) return `${Math.floor(sec/60)}m ago`;
return `${Math.floor(sec/3600)}h ago`;
}
function severityColor(s) {
return {CRITICAL:'#ef4444', HIGH:'#f97316', MEDIUM:'#eab308', LOW:'#22c55e'}[s] || '#94a3b8';
}
function renderAlerts(alerts) {
const tbody = document.getElementById('alerts-tbody');
const noAlerts = document.getElementById('no-alerts');
const visible = currentFilter === 'all' ? alerts : alerts.filter(a => a.severity === currentFilter);
if (!visible.length) {
tbody.innerHTML = '';
noAlerts.style.display = 'block';
return;
}
noAlerts.style.display = 'none';
tbody.innerHTML = visible.map(a => `
<tr>
<td class="ts-text">${String(a.id).padStart(3,'0')}</td>
<td><span class="badge badge-${a.severity}">${a.severity}</span></td>
<td><span class="type-tag">${a.type}</span></td>
<td><span class="ip-text">${a.ip}</span></td>
<td><div class="detail-text" title="${a.detail}">${a.detail}</div>
<div style="font-size:.75rem;color:var(--text-muted);margin-top:.25rem">${a.source}</div></td>
<td><span class="count-badge">×${a.count}</span></td>
<td class="ts-text">${timeSince(a.last_seen)}</td>
</tr>
`).join('');
}
function renderIPList(alerts) {
const ipMap = {};
alerts.forEach(a => {
if (a.ip === 'unknown') return;
if (!ipMap[a.ip]) ipMap[a.ip] = { count:0, maxSev:'LOW' };
ipMap[a.ip].count += a.count;
const order = {CRITICAL:4,HIGH:3,MEDIUM:2,LOW:1};
if ((order[a.severity]||0) > (order[ipMap[a.ip].maxSev]||0)) ipMap[a.ip].maxSev = a.severity;
});
const sorted = Object.entries(ipMap).sort((a,b) => b[1].count - a[1].count).slice(0,8);
const maxCount = sorted[0]?.[1]?.count || 1;
document.getElementById('ip-list').innerHTML = sorted.length
? sorted.map(([ip,{count,maxSev}]) => `
<div class="ip-row">
<div class="ip-info">
<div class="threat-dot" style="background:${severityColor(maxSev)}"></div>
<span class="ip-text">${ip}</span>
</div>
<div class="alert-count-bar">
<div class="bar-track"><div class="bar-fill" style="width:${Math.round(count/maxCount*100)}%;background:${severityColor(maxSev)}"></div></div>
<span>${count} events</span>
</div>
</div>`).join('')
: '<div style="color:var(--text-muted);font-size:.85rem;padding:.5rem 0">No suspicious IPs detected</div>';
}
function renderActivityLog(alerts) {
const recent = [...alerts]
.sort((a,b) => new Date(b.last_seen) - new Date(a.last_seen))
.slice(0,8);
document.getElementById('activity-log').innerHTML = recent.map(a => `
<div class="log-entry">
<span class="log-time">${formatTime(a.last_seen)}</span>
<span class="log-msg" style="color:${severityColor(a.severity)}">[${a.severity}]</span>
<span class="log-msg">${a.type.replace(/_/g,' ')}</span>
<span class="log-src">${a.source}</span>
</div>`).join('') || '<div style="color:var(--text-muted);font-size:.85rem">No activity</div>';
}
function updateStats(data) {
document.getElementById('stat-total').textContent = data.total_alerts;
document.getElementById('stat-critical').textContent = data.critical_count;
document.getElementById('stat-high').textContent = data.high_count;
document.getElementById('stat-medium').textContent = data.medium_count;
const uniqueIPs = new Set(data.alerts.map(a=>a.ip).filter(i=>i!=='unknown')).size;
document.getElementById('stat-ips').textContent = uniqueIPs;
document.getElementById('last-updated').textContent =
new Date(data.generated_at).toLocaleTimeString('en-GB');
}
async function loadAlerts() {
try {
const res = await fetch('./alerts.json?v=' + Date.now());
const data = await res.json();
allAlerts = data.alerts || [];
updateStats(data);
renderAlerts(allAlerts);
renderIPList(allAlerts);
renderActivityLog(allAlerts);
} catch {
// Demo mode — use built-in sample data
allAlerts = DEMO_DATA.alerts;
updateStats(DEMO_DATA);
renderAlerts(allAlerts);
renderIPList(allAlerts);
renderActivityLog(allAlerts);
}
}
function filterAlerts(severity, btn) {
currentFilter = severity;
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderAlerts(allAlerts);
}
// Auto-refresh every 30s
loadAlerts();
setInterval(loadAlerts, 30000);
</script>
</body>
</html>
-324
View File
@@ -1,324 +0,0 @@
# Lightweight SIEM Deployment Guide on Ghaymah Cloud Infrastructure
## Overview
This guide explains how to deploy the `siem.py` log analyzer and `dashboard.html` on Ghaymah cloud infrastructure, using Block Storage to persist logs and alert data.
---
## Architecture
```mermaid
graph TD
BS["🗄️ Block Storage Volume\n/mnt/logs"]
BS --> AL["/mnt/logs/auth.log"]
BS --> NL["/mnt/logs/nginx/access.log"]
BS --> AP["/mnt/logs/app/application.log"]
AL & NL & AP --> SIEM["⚙️ siem.py\nsystemd service"]
SIEM -->|"writes every 30s"| AJ["📄 alerts.json\n/var/www/siem/"]
AJ -->|"served by Nginx"| DASH["🖥️ dashboard.html\nNear Real-Time UI"]
```
---
## Step 1: Create and Attach Block Storage
In the Ghaymah Cloud Console:
1. Go to **Storage → Block Storage → Create Volume**
2. Set name: `siem-logs-volume`
3. Set size: `50 GB` (adjust based on log retention needs)
4. Attach to your cloud instance
Then format and mount the volume:
```bash
# Find the new volume (usually /dev/sdb or /dev/vdb)
lsblk
# Format the volume
sudo mkfs.ext4 /dev/sdb
# Create mount point
sudo mkdir -p /mnt/logs
# Mount the volume
sudo mount /dev/sdb /mnt/logs
# Persist mount across reboots — use UUID to avoid /dev/sdX changes after reboot
# First find the UUID:
sudo blkid /dev/sdb
# Then replace UUID=xxxx with the actual value shown:
echo 'UUID=xxxx-xxxx /mnt/logs ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab
# Create log directories
sudo mkdir -p /mnt/logs/nginx /mnt/logs/app
sudo chown -R $USER:$USER /mnt/logs
```
> **Encryption:** Enable volume encryption during Block Storage creation in the Ghaymah console before writing any data to the volume.
---
## Step 2: Configure Services to Write Logs to Block Storage
### Nginx
```bash
# Edit nginx.conf to write access log to block storage
sudo nano /etc/nginx/nginx.conf
# Change:
# access_log /var/log/nginx/access.log;
# To:
# access_log /mnt/logs/nginx/access.log;
sudo systemctl reload nginx
```
### Application (Docker)
```yaml
# docker-compose.yml
services:
api:
volumes:
- /mnt/logs/app:/var/log/app
environment:
LOG_PATH: /var/log/app/application.log
```
> **Secrets management note:** For production deployments, avoid plain `.env` files on disk.
> Prefer **Kubernetes Secrets** (mounted as env vars or files) or a cloud secret manager
> (e.g., HashiCorp Vault, AWS Secrets Manager, or your cloud provider's equivalent).
> `.env` files are acceptable for local development but should never be committed to Git.
### SSH Auth Log Forwarding
```bash
# Create the auth log file with correct permissions for rsyslog
sudo touch /mnt/logs/auth.log
sudo chown syslog:adm /mnt/logs/auth.log
sudo chmod 640 /mnt/logs/auth.log
# Direct rsyslog to write auth logs to block storage
echo 'auth,authpriv.* /mnt/logs/auth.log' | sudo tee /etc/rsyslog.d/99-siem.conf
sudo systemctl restart rsyslog
```
---
## Step 3: Deploy siem.py as a Systemd Service
```bash
# Install Python 3
sudo apt-get update && sudo apt-get install -y python3
# Create a shared group so both siem.py (ubuntu) and nginx (www-data) can access alerts.json
sudo groupadd siem
sudo usermod -aG siem ubuntu
sudo usermod -aG siem www-data
# Copy SIEM files
sudo mkdir -p /opt/siem /var/www/siem
sudo cp q4-siem/siem.py /opt/siem/siem.py
sudo cp q4-siem/dashboard.html /var/www/siem/dashboard.html
# Set permissions: ubuntu writes, www-data reads via shared siem group
sudo touch /var/www/siem/alerts.json
sudo chown ubuntu:siem /var/www/siem/alerts.json
sudo chmod 640 /var/www/siem/alerts.json
# Edit log paths in siem.py to match block storage
sudo nano /opt/siem/siem.py
# Set:
# LOG_SOURCES = {
# "auth": "/mnt/logs/auth.log",
# "nginx": "/mnt/logs/nginx/access.log",
# "app": "/mnt/logs/app/application.log",
# }
# OUTPUT_FILE = "/var/www/siem/alerts.json"
```
Create the systemd service:
```ini
# /etc/systemd/system/siem.service
[Unit]
Description=Ghaymah SIEM Log Analyzer
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/siem
ExecStart=/usr/bin/python3 /opt/siem/siem.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable siem
sudo systemctl start siem
sudo systemctl status siem
```
---
## Step 4: Serve the Dashboard via Nginx
```nginx
# /etc/nginx/sites-available/siem
server {
listen 8443 ssl;
server_name siem.ghaymah.systems;
ssl_certificate /etc/letsencrypt/live/siem.ghaymah.systems/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/siem.ghaymah.systems/privkey.pem;
root /var/www/siem;
index dashboard.html;
# Restrict access to internal network only
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
# alerts.json is served to the dashboard browser client.
# The parent allow/deny block already restricts access to internal IPs only.
location = /alerts.json {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
add_header Content-Type application/json;
}
location / {
try_files $uri $uri/ =404;
}
}
```
```bash
sudo ln -s /etc/nginx/sites-available/siem /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
---
## Step 5: Log Rotation
```bash
# /etc/logrotate.d/siem-logs
/mnt/logs/*.log
/mnt/logs/app/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 syslog adm
postrotate
systemctl restart rsyslog
endscript
}
/mnt/logs/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 www-data adm
postrotate
systemctl reload nginx
endscript
}
```
---
## Verification
```bash
# Check SIEM is running
sudo systemctl status siem
# Check alerts output
cat /var/www/siem/alerts.json | python3 -m json.tool | head -30
# View SIEM logs
sudo journalctl -u siem -f
# Test dashboard
curl -k https://siem.ghaymah.systems:8443/dashboard.html
```
---
## Security Notes
| Control | Implementation |
|---|---|
| Dashboard access | Restricted to internal IP range (10.0.0.0/8) |
| HTTPS | TLS required, no HTTP access |
| `alerts.json` access | Internal IPs only via `allow 10.0.0.0/8; deny all;` inside `location = /alerts.json` |
| File permissions | `alerts.json` owned `ubuntu:siem`, mode `640` — owner writes, group (nginx) reads, others none |
| Block storage | Encrypted at rest (enable during volume creation in Ghaymah console) |
| Audit logs | All SIEM activity written to systemd journal |
---
## Future Integrations
The SIEM is designed to grow. The following integrations can be added as next steps:
| Integration | How |
|---|---|
| **AlertManager webhook** | Extend `siem.py` to POST critical alerts to a webhook endpoint. Pairs directly with the Prometheus alert rules in `q2-attack-simulation/alert-rule.yml`. |
| **Slack notifications** | Send alerts to a `#security-alerts` channel via Slack Incoming Webhooks when severity is CRITICAL or HIGH. |
| **PagerDuty / on-call** | Route CRITICAL alerts to PagerDuty for 24/7 on-call escalation using the Events API. |
| **Email alerts** | Use `smtplib` in `siem.py` to send summary emails to the security team at the end of each scan cycle. |
| **Cloud monitoring export** | Ship `alerts.json` to a cloud-native monitoring service (e.g., Ghaymah monitoring, Datadog, or CloudWatch) for long-term trend analysis and dashboards. |
| **Log shipping to SIEM platform** | Forward structured logs to a dedicated SIEM platform (e.g., Elastic/OpenSearch) for retention and correlation across multiple services. |
---
## Backup Strategy
Block Storage volumes are the persistence layer for all logs and alert data. Schedule regular snapshots to protect against data loss.
**Recommended snapshot schedule:**
| Frequency | Retention | Purpose |
|---|---|---|
| Daily | 7 days | Short-term recovery from accidental deletion or corruption |
| Weekly | 4 weeks | Recovery from persistent misconfiguration issues |
| Monthly | 3 months | Compliance and long-term trend analysis |
**How to enable in Ghaymah console:**
1. Go to **Storage → Block Storage → `siem-logs-volume`**
2. Navigate to **Snapshots → Create Snapshot Policy**
3. Set a daily schedule with 7-day retention
4. Enable email notification on snapshot failure
**Restore procedure (monthly test recommended):**
```bash
# Restore a snapshot to a new volume in the Ghaymah console,
# then mount it temporarily to verify data integrity:
sudo mkdir -p /mnt/logs-restore
sudo mount /dev/sdc /mnt/logs-restore
ls -lh /mnt/logs-restore/
sudo umount /mnt/logs-restore
```
> Restore tests should be performed monthly to confirm backups are valid and recovery time is within acceptable limits.
-302
View File
@@ -1,302 +0,0 @@
#!/usr/bin/env python3
"""
siem.py — Simplified SIEM for Ghaymah Storage
Collects logs from 3 sources, analyzes for suspicious patterns,
and exposes alerts as JSON for the dashboard.
"""
import re
import json
import os
import time
import random
import logging
from datetime import datetime, timedelta
from collections import defaultdict
from pathlib import Path
# ─── Configuration ─────────────────────────────────────────────
LOG_SOURCES = {
"auth": "/mnt/logs/auth.log",
"nginx": "/mnt/logs/nginx/access.log",
"app": "/mnt/logs/app/application.log",
}
OUTPUT_FILE = "/var/www/siem/alerts.json"
ALERT_THRESHOLD_FAIL = 5 # Failed logins before alert
ALERT_THRESHOLD_RATE = 100 # HTTP requests/min before alert
SCAN_INTERVAL_SEC = 30 # How often to re-scan logs
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
log = logging.getLogger("siem")
# ─── Patterns ──────────────────────────────────────────────────
PATTERNS = {
"brute_force": re.compile(
r"Failed password for .+ from (?P<ip>\d+\.\d+\.\d+\.\d+)"
),
"invalid_user": re.compile(
r"Invalid user .+ from (?P<ip>\d+\.\d+\.\d+\.\d+)"
),
"nginx_request": re.compile(
r'(?P<ip>\d+\.\d+\.\d+\.\d+) .+ "(?P<method>\w+) (?P<path>\S+) HTTP.+" (?P<status>\d{3})'
),
"app_error": re.compile(
r"\[(?P<level>ERROR|CRITICAL)\] (?P<msg>.+)"
),
"sql_injection": re.compile(
r"\b(select|union|insert|drop|update|delete|benchmark|sleep)\b\s*[\(%'\"()]",
re.IGNORECASE
),
}
# ─── State ─────────────────────────────────────────────────────
failed_logins: dict = defaultdict(int) # ip → count
request_counts: dict = defaultdict(int) # ip → count/window
sql_attempts: list = []
alerts: list = []
def generate_sample_logs():
"""
Generate sample log lines (used when real log files don't exist).
This allows the SIEM to demonstrate functionality without root access.
"""
attacker_ip = "185.220.100.42"
legit_ip = "192.168.1.10"
internal_ip = "10.0.0.5"
sample_lines = {
"auth": [
f"Jul 26 19:01:00 server sshd[1234]: Failed password for root from {attacker_ip} port 54321 ssh2",
f"Jul 26 19:01:02 server sshd[1234]: Failed password for admin from {attacker_ip} port 54321 ssh2",
f"Jul 26 19:01:04 server sshd[1234]: Failed password for ubuntu from {attacker_ip} port 54321 ssh2",
f"Jul 26 19:01:06 server sshd[1234]: Failed password for user from {attacker_ip} port 54321 ssh2",
f"Jul 26 19:01:08 server sshd[1234]: Failed password for deploy from {attacker_ip} port 54321 ssh2",
f"Jul 26 19:01:10 server sshd[1234]: Failed password for git from {attacker_ip} port 54321 ssh2",
f"Jul 26 19:01:12 server sshd[1234]: Invalid user hacker from {attacker_ip} port 54321",
f"Jul 26 19:05:00 server sshd[5678]: Accepted password for deploy from {legit_ip} port 22122 ssh2",
],
"nginx": [
f'185.220.100.42 - - [26/Jul/2026:19:01:00 +0300] "POST /api/v1/auth/login HTTP/1.1" 401 120',
f'185.220.100.42 - - [26/Jul/2026:19:01:01 +0300] "POST /api/v1/auth/login HTTP/1.1" 401 120',
f'185.220.100.42 - - [26/Jul/2026:19:01:02 +0300] "POST /api/v1/auth/login HTTP/1.1" 401 120',
f'185.220.100.42 - - [26/Jul/2026:19:01:03 +0300] "GET /api/v1/users?id=1 UNION SELECT * FROM users-- HTTP/1.1" 400 80',
f'192.168.1.10 - - [26/Jul/2026:19:02:00 +0300] "GET /dashboard HTTP/1.1" 200 4096',
f'10.0.0.5 - - [26/Jul/2026:19:03:00 +0300] "GET /health HTTP/1.1" 200 32',
],
"app": [
"[ERROR] Database connection timeout after 30s — retrying",
"[CRITICAL] Unhandled exception in /api/v1/users/export: PermissionError",
"[ERROR] JWT verification failed for token eyJ0eXAi... from IP 185.220.100.42",
"[INFO] User admin@ghaymah.systems logged in from 192.168.1.10",
"[ERROR] SQL query failed: syntax error near 'UNION'",
],
}
return sample_lines
def read_log_source(name: str, path: str) -> list[str]:
"""Read lines from a log file, or fall back to sample data."""
if os.path.exists(path):
try:
with open(path, "r", errors="replace") as f:
lines = f.readlines()
log.info(f"[{name}] Read {len(lines)} lines from {path}")
return lines
except PermissionError:
log.warning(f"[{name}] Permission denied reading {path} — using sample data")
else:
log.warning(f"[{name}] File not found: {path} — using sample data")
# Return sample data
return generate_sample_logs().get(name, [])
def analyze_auth_log(lines: list[str]):
"""Detect brute force SSH attempts."""
for line in lines:
m = PATTERNS["brute_force"].search(line)
if m:
ip = m.group("ip")
failed_logins[ip] += 1
m = PATTERNS["invalid_user"].search(line)
if m:
ip = m.group("ip")
failed_logins[ip] += 1
for ip, count in failed_logins.items():
if count >= ALERT_THRESHOLD_FAIL:
create_alert(
source="auth.log",
alert_type="brute_force_ssh",
severity="CRITICAL",
ip=ip,
detail=f"{count} failed SSH login attempts detected",
)
def analyze_nginx_log(lines: list[str]):
"""Detect HTTP flooding and SQL injection attempts."""
ip_requests = defaultdict(list)
for line in lines:
m = PATTERNS["nginx_request"].search(line)
if m:
ip = m.group("ip")
path = m.group("path")
status = m.group("status")
ip_requests[ip].append((path, status))
# Check for SQL injection in path
if PATTERNS["sql_injection"].search(path):
create_alert(
source="nginx/access.log",
alert_type="sql_injection_attempt",
severity="HIGH",
ip=ip,
detail=f"Possible SQL injection detected in request path: {path[:120]}",
)
# Check high request rate
for ip, reqs in ip_requests.items():
failed = [r for r in reqs if r[1].startswith(("4", "5"))]
if len(reqs) >= ALERT_THRESHOLD_RATE:
create_alert(
source="nginx/access.log",
alert_type="http_flood",
severity="HIGH",
ip=ip,
detail=f"{len(reqs)} requests detected in log window ({len(failed)} errors)",
)
elif len(failed) >= ALERT_THRESHOLD_FAIL:
create_alert(
source="nginx/access.log",
alert_type="repeated_http_errors",
severity="MEDIUM",
ip=ip,
detail=f"{len(failed)} HTTP error responses to IP in log window",
)
def analyze_app_log(lines: list[str]):
"""Detect application-level errors and anomalies."""
for line in lines:
m = PATTERNS["app_error"].search(line)
if m:
level = m.group("level")
msg = m.group("msg")
# Extract IP if present
ip_match = re.search(r"\d+\.\d+\.\d+\.\d+", msg)
ip = ip_match.group(0) if ip_match else "unknown"
if "JWT" in msg or "token" in msg.lower():
severity = "HIGH"
elif level == "CRITICAL":
severity = "CRITICAL"
else:
severity = "MEDIUM"
create_alert(
source="app/application.log",
alert_type="application_error",
severity=severity,
ip=ip,
detail=msg[:200],
)
def create_alert(source: str, alert_type: str, severity: str, ip: str, detail: str):
"""Create a deduplicated alert entry."""
# Deduplicate: only merge if same type + IP + first 60 chars of detail match.
# This prevents a low-severity alert from absorbing a different high-severity one.
for existing in alerts:
if (existing["type"] == alert_type
and existing["ip"] == ip
and existing["detail"][:60] == detail[:60]):
existing["count"] += 1
existing["last_seen"] = datetime.now().isoformat()
return
alert_entry = {
"id": len(alerts) + 1,
"timestamp": datetime.now().isoformat(),
"last_seen": datetime.now().isoformat(),
"source": source,
"type": alert_type,
"severity": severity,
"ip": ip,
"detail": detail,
"count": 1,
"status": "open",
}
alerts.append(alert_entry)
log.warning(f"[ALERT] [{severity}] {alert_type} — IP: {ip} — {detail[:80]}")
def run_scan():
"""Main scan loop — reads all 3 log sources and runs analysis.
NOTE: This implementation is stateless — alert history resets on each
scan cycle. This is acceptable for demo/project use. Production deployments
should persist alert history in a database or SIEM backend (e.g.,
Elasticsearch/OpenSearch) to retain trends and support correlation rules.
"""
global alerts, failed_logins
alerts = []
failed_logins = defaultdict(int)
log.info("Starting SIEM scan cycle...")
# Source 1: Auth logs
auth_lines = read_log_source("auth", LOG_SOURCES["auth"])
analyze_auth_log(auth_lines)
# Source 2: Nginx access logs
nginx_lines = read_log_source("nginx", LOG_SOURCES["nginx"])
analyze_nginx_log(nginx_lines)
# Source 3: Application logs
app_lines = read_log_source("app", LOG_SOURCES["app"])
analyze_app_log(app_lines)
# Sort by severity
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
alerts.sort(key=lambda a: severity_order.get(a["severity"], 9))
output = {
"generated_at": datetime.now().isoformat(),
"total_alerts": len(alerts),
"critical_count": sum(1 for a in alerts if a["severity"] == "CRITICAL"),
"high_count": sum(1 for a in alerts if a["severity"] == "HIGH"),
"medium_count": sum(1 for a in alerts if a["severity"] == "MEDIUM"),
"alerts": alerts,
}
with open(OUTPUT_FILE, "w") as f:
json.dump(output, f, indent=2)
log.info(f"Scan complete. {len(alerts)} alerts written to {OUTPUT_FILE}")
return output
if __name__ == "__main__":
log.info("=== Ghaymah SIEM Starting ===")
log.info(f"Monitoring: {', '.join(LOG_SOURCES.values())}")
log.info(f"Output: {OUTPUT_FILE}")
while True:
try:
results = run_scan()
print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Alerts: {results['total_alerts']} "
f"(CRITICAL: {results['critical_count']}, HIGH: {results['high_count']}, "
f"MEDIUM: {results['medium_count']})")
except KeyboardInterrupt:
log.info("SIEM stopped by user.")
break
except Exception as e:
log.error(f"Scan error: {e}")
time.sleep(SCAN_INTERVAL_SEC)
@@ -1,163 +0,0 @@
# Ghaymah Backup & Recovery Strategy
## RPO and RTO Targets
| Metric | Definition | Target for Ghaymah |
|---|---|---|
| **RPO** (Recovery Point Objective) | Maximum acceptable data loss (how old can a backup be?) | **1 hour** — automatic snapshots every hour |
| **RTO** (Recovery Time Objective) | Maximum acceptable downtime (how fast must we recover?) | **4 hours** — from incident declaration to restored service |
---
## The 3-2-1 Backup Rule
The **3-2-1 rule** is a commonly used backup best practice:
```
3 — Keep 3 copies of your data
2 — Store them on 2 different types of media/storage
1 — Keep 1 copy offsite (geographically separate location)
```
### How Ghaymah Implements 3-2-1
```mermaid
graph TD
subgraph Copy 1 - Primary
LIVE["💻 Production Instance"]
BS["🗄️ SSD Block Storage\n(Attached Volume)"]
LIVE --> BS
end
subgraph Copy 2 - Cloud Backup
OS["☁️ Ghaymah Object Storage\n(Separate Storage Tier)"]
BS -->|"Automated Hourly Backup\nRetention: 7 days hourly, 30 days daily"| OS
end
subgraph Copy 3 - Offsite Remote
OFF["🌍 Cold/Archive Storage\n(e.g., Secondary Regional Datacenter)"]
OS -->|"Daily Export\nRetention: 90 days"| OFF
end
```
---
## Backup Schedule
| Backup Type | Frequency | Retention | Storage Location |
|---|---|---|---|
| Ghaymah Object Storage backup | Every 1 hour | 7 days | Ghaymah Object Storage (separate from Block Storage) |
| Daily backup | Every 24 hours at 02:00 | 30 days | Ghaymah Object Storage |
| Weekly backup | Every Sunday at 03:00 | 90 days | Offsite / remote region |
| Pre-deployment backup | Before every deployment | 30 days | Ghaymah Object Storage |
---
## Automated Snapshot Script
```bash
#!/bin/bash
# backup-snapshot.sh — Run via cron every hour
VOLUME_ID="vol-xxxx-replace-with-actual"
SNAPSHOT_PREFIX="auto-backup"
RETENTION_DAYS=7
DATE=$(date +%Y%m%d-%H%M)
echo "[$(date)] Creating snapshot for volume $VOLUME_ID"
# Create snapshot (replace with actual Ghaymah CLI command)
ghaymah-cli storage snapshot create \
--volume-id "$VOLUME_ID" \
--name "${SNAPSHOT_PREFIX}-${DATE}"
# Clean up snapshots older than retention window
ghaymah-cli storage snapshot list --volume-id "$VOLUME_ID" \
| awk -v cutoff="$(date -d "$RETENTION_DAYS days ago" +%s)" \
'$3 < cutoff {print $1}' \
| xargs -I{} ghaymah-cli storage snapshot delete --snapshot-id {}
echo "[$(date)] Snapshot created: ${SNAPSHOT_PREFIX}-${DATE}"
```
Add to crontab:
```bash
# Run backup every hour at minute 0
0 * * * * /opt/scripts/backup-snapshot.sh >> /var/log/backup.log 2>&1
```
---
## Recovery Procedure
### Standard Recovery (planned or scheduled)
```bash
# 1. List available snapshots
ghaymah-cli storage snapshot list --volume-id vol-xxxx
# 2. Create new volume from snapshot
ghaymah-cli storage volume create-from-snapshot \
--snapshot-id snap-xxxx \
--name recovery-$(date +%Y%m%d)
# 3. Detach old volume (if still attached)
ghaymah-cli storage volume detach --instance-id inst-xxxx --volume-id vol-old
# 4. Attach new volume
ghaymah-cli storage volume attach --instance-id inst-xxxx --volume-id vol-new
# 5. Mount and verify
sudo mount /dev/sdb /mnt/data
ls -la /mnt/data
```
### Emergency Recovery (ransomware or corruption)
1. Use emergency-plan.md for first 60 minutes.
2. Identify last clean snapshot from **before** the incident timestamp.
3. Never mount suspicious volumes to production instances.
4. Always verify restored data integrity before going live.
---
## Backup Integrity Verification
Monthly verification test:
```bash
#!/bin/bash
# verify-backup.sh — Monthly DR drill
echo "=== Backup Verification Test ==="
echo "Date: $(date)"
# 1. Identify latest snapshot
LATEST_SNAP=$(ghaymah-cli storage snapshot list --volume-id vol-xxxx \
| sort -k3 -r | head -1 | awk '{print $1}')
# 2. Create test restore volume
ghaymah-cli storage volume create-from-snapshot \
--snapshot-id "$LATEST_SNAP" \
--name test-restore-$(date +%Y%m%d)
# 3. Attach to test instance (never production)
ghaymah-cli storage volume attach --instance-id inst-test --volume-id vol-test
# 4. Verify expected file structure
EXPECTED_COUNT=$(cat /opt/backup-manifest/expected-file-count.txt)
ACTUAL_COUNT=$(find /mnt/test-restore -type f | wc -l)
echo "Expected files: $EXPECTED_COUNT"
echo "Actual files: $ACTUAL_COUNT"
if [ "$ACTUAL_COUNT" -ge "$EXPECTED_COUNT" ]; then
echo "[PASS] Backup integrity verified"
else
echo "[FAIL] File count mismatch — alert secops team"
fi
# 5. Cleanup test volume
ghaymah-cli storage volume detach --instance-id inst-test --volume-id vol-test
ghaymah-cli storage volume delete --volume-id vol-test
```
-130
View File
@@ -1,130 +0,0 @@
# Emergency Plan — First 60 Minutes of Ransomware Incident
> **Scenario:** All Block Storage files on Ghaymah cloud are encrypted with a ransom message.
---
## ⏱ Minute 0–5: Detect & Confirm
**Trigger Indicators:**
- Files renamed to `*.encrypted` or `*.locked`
- Ransom note file: `READ_ME_TO_DECRYPT.txt`
- Sudden spike in Block Storage write IOPS
- Applications returning file-not-found errors
**Immediate Actions:**
1. Alert the security team and management — declare a P1 incident.
2. Do NOT pay the ransom at this stage.
3. Do NOT reboot or shut down the instance (preserves memory evidence).
4. Take a forensic snapshot of the current Block Storage volume to preserve evidence. **Do not restore from this snapshot** — it may contain encrypted files. Restore from the last clean backup taken before the encryption event.
```bash
# Forensic snapshot — evidence preservation only
ghaymah-cli storage snapshot create --volume-id vol-xxxx --name ransomware-forensic-$(date +%Y%m%d)
```
---
## ⏱ Minute 5–15: Isolate
**Goal:** Stop the ransomware from spreading to other systems.
| Action | Method |
|---|---|
| Isolate the infected instance | Remove from load balancer pool immediately |
| Block all outbound traffic | Apply deny-all egress firewall rule to the instance |
| Revoke instance API credentials | Rotate IAM keys/tokens used by the instance |
| Disconnect Block Storage | Detach volume to prevent further encryption |
| Alert other teams | Notify DB admins, network team, management |
```bash
# Detach Block Storage volume
ghaymah-cli storage volume detach --instance-id inst-xxxx --volume-id vol-xxxx
# Apply deny-all network rule (Ghaymah firewall)
ghaymah-cli network firewall add-rule \
--instance inst-xxxx \
--direction egress \
--action deny \
--priority 1
```
---
## ⏱ Minute 15–30: Assess the Damage
**Goal:** Understand the scope of encryption and identify the attack vector.
1. **Check which files are encrypted:**
```bash
# On a forensic copy only
find /mnt/forensic-copy -name "*.encrypted" -o -name "*.locked" | wc -l
```
2. **Identify ransomware family** (from ransom note + file extensions).
3. **Check intrusion timeline** — review access logs from the past 48 hours:
```bash
grep -i "POST\|PUT" /var/log/nginx/access.log | tail -500
journalctl --since "48 hours ago" | grep -E "error|fail|unauthorized"
```
4. **Identify the attack vector:**
- Phishing email with malicious attachment?
- Exposed RDP/SSH with weak credentials?
- Vulnerable web application?
- Compromised supply chain dependency?
5. **Check for legitimate decryption tools:** Search security databases (NoMoreRansom.org) — some ransomware strains have free decryptors.
---
## ⏱ Minute 30–45: Begin Recovery
**Goal:** Restore services from the last clean backup.
1. Identify the last clean backup snapshot (before encryption):
```bash
ghaymah-cli storage snapshot list --volume-id vol-xxxx
# Look for the last snapshot BEFORE the ransomware event timestamp
```
2. Create a new volume from the clean snapshot:
```bash
ghaymah-cli storage volume create-from-snapshot \
--snapshot-id snap-clean-xxxx \
--name restored-volume-$(date +%Y%m%d)
```
3. Attach the restored volume to a clean, rebuilt instance.
4. Verify data integrity before bringing services back online:
```bash
# Check file counts match expected
find /mnt/restored -type f | wc -l
# Run application health checks
curl -f http://localhost:8080/health
```
---
## ⏱ Minute 45–60: Restore Service & Communicate
1. Re-add restored instance to the load balancer.
2. Monitor for any re-infection indicators (file modification spikes).
3. Send internal status update: scope, services affected, ETA for full recovery.
4. Prepare customer/stakeholder disclosure notification (if PII was exposed).
5. Preserve the forensic snapshot for later analysis and potential law enforcement reporting.
---
## Incident Commander Checklist
- [ ] P1 alert sent to all stakeholders
- [ ] Infected instance isolated (firewall + load balancer)
- [ ] Block Storage detached
- [ ] Forensic snapshot taken
- [ ] Clean backup identified
- [ ] Restoration in progress
- [ ] Communication sent to affected parties
- [ ] Legal/compliance team notified
-167
View File
@@ -1,167 +0,0 @@
# Comprehensive Ransomware Prevention Plan
> Goal: Eliminate the key attack vectors that allow ransomware to reach and encrypt Ghaymah Block Storage.
---
## Prevention Layer 1: Access Control & Authentication
### 1.1 Eliminate Password-Only Authentication
- **Replace SSH passwords with SSH keys** for all server access.
- Disable password-based SSH login:
```bash
# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
```
- Restrict SSH access to a VPN or bastion host only.
### 1.2 Enforce Multi-Factor Authentication (MFA)
- Require MFA for:
- Cloud console / Ghaymah dashboard access
- All admin and privileged user accounts
- CI/CD pipeline credentials
### 1.3 Principle of Least Privilege
- Application containers and services should **never** have Block Storage mount access unless explicitly required.
- Use separate IAM roles: one for read, one for write, none for delete by default.
- Administrative access to cloud resources requires MFA + session time limit.
---
## Prevention Layer 2: Endpoint & System Hardening
### 2.1 Keep Systems Patched
```bash
# Automate security updates (Ubuntu/Debian)
sudo apt-get install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
```
### 2.2 Restrict Executable Permissions
- Mount Block Storage with `noexec` flag to prevent direct execution of files from storage:
```bash
# /etc/fstab
/dev/sdb /mnt/data ext4 defaults,noexec,nosuid 0 2
```
- This means even if a ransomware binary is uploaded to Block Storage, it cannot run directly from there.
### 2.3 Disable Unused Services
```bash
# Disable unnecessary services
sudo systemctl disable --now telnet ftp rpcbind
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH from known IPs only
sudo ufw allow 443/tcp # HTTPS
```
---
## Prevention Layer 3: Network Security
### 3.1 Segment the Network
- Block Storage volumes should only be attached to authorized instances and protected from unauthorized access.
- Use private subnets:
```
Public subnet: Load Balancer, API Gateway
Private subnet: Application Servers, Block Storage
Database subnet: PostgreSQL, Redis (no internet access)
```
### 3.2 Block Lateral Movement
Apply Ghaymah network policies to prevent instances from communicating with each other unless explicitly needed:
```yaml
# Network policy: deny all east-west traffic by default
# Only allow app-server → database on port 5432
# Only allow app-server → block-storage on mount connection
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-lateral-movement
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
egress:
- to:
- podSelector:
matchLabels:
tier: database
ports:
- port: 5432
```
### 3.3 DNS Filtering & Egress Control
- Implement DNS-based blocking to prevent ransomware C2 (command-and-control) communication.
- Block outbound traffic to known malicious domains using a threat intelligence feed.
- Allow-list only required outbound destinations (e.g., update servers, CDNs).
---
## Prevention Layer 4: Email & Social Engineering Defense
| Control | Implementation |
|---|---|
| Email filtering | Block executable attachments (.exe, .bat, .ps1, .vbs) |
| Link scanning | Scan all URLs in emails before delivery |
| Phishing simulation | Run quarterly phishing drills with staff |
| Security awareness training | Train all employees on ransomware recognition |
| DMARC/SPF/DKIM | Prevent email spoofing using DNS mail security records |
---
## Prevention Layer 5: Monitoring & Early Detection
### 5.1 File Integrity Monitoring (FIM)
Monitor Block Storage for unusual file rename patterns:
```bash
# Install auditd for file system monitoring
sudo apt-get install auditd -y
# Watch for mass file renames (ransomware signature)
auditctl -w /mnt/data -p wra -k file_modification
# Alert on rename events
ausearch -k file_modification | grep "rename"
```
### 5.2 SIEM Alerts (from Q4)
Configure the SIEM (`siem.py`) to detect early ransomware indicators:
- Sudden spike in Block Storage write IOPS (>1000 writes/minute)
- Mass file extension changes
- New processes accessing large numbers of files in seconds
- Outbound connections to unknown external IPs from storage-tier containers
### 5.3 Immutable Block Storage Snapshots
Configure snapshots with **Object Lock** (immutable mode) so they cannot be deleted — even by an admin account — for a defined retention period:
```bash
# Example command — replace with actual Ghaymah immutable storage configuration
ghaymah-cli storage bucket set-object-lock \
--bucket siem-backups \
--mode COMPLIANCE \
--retention-days 30
```
---
## Prevention Checklist Summary
| Priority | Control | Status |
|---|---|---|
| Critical | SSH key-only authentication | Implement immediately |
| Critical | MFA for cloud console | Implement immediately |
| Critical | Least-privilege IAM roles | Implement immediately |
| High | Automated OS patching | This week |
| High | Network segmentation (public/private subnets) | This week |
| High | `noexec` on Block Storage mounts | This week |
| High | Immutable backup snapshots | This week |
| Medium | DNS filtering / egress control | This month |
| Medium | File Integrity Monitoring (FIM) | This month |
| Medium | Phishing simulation program | This quarter |
| Low | Security awareness training | This quarter |