nordabiz/docs/SECURITY.md
Maciej Pienczyn 110d971dca
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat: migrate prod docs to OVH VPS + UTC→Warsaw timezone in all templates
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS
(57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash
commands, memory files, architecture docs, and deploy procedures.

Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted
155 .strftime() calls across 71 templates so timestamps display
in Polish timezone regardless of server timezone.

Also includes: created_by_id tracking, abort import fix, ICS
calendar fix for missing end times, Pros Poland data cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:41:53 +02:00

637 lines
17 KiB
Markdown

# Security Guide - Norda Biznes Partner
**Last Updated:** 2026-01-10
**Status:** Active
**Severity:** CRITICAL - Follow all guidelines
---
## Table of Contents
1. [Overview](#overview)
2. [Database Credentials Management](#database-credentials-management)
3. [Environment Variables Reference](#environment-variables-reference)
4. [Development Environment Setup](#development-environment-setup)
5. [Production Environment Setup](#production-environment-setup)
6. [Shell Script Configuration](#shell-script-configuration)
7. [Security Best Practices](#security-best-practices)
8. [Verification and Testing](#verification-and-testing)
9. [Troubleshooting](#troubleshooting)
10. [Incident Response](#incident-response)
---
## Overview
This document provides comprehensive guidance on securely configuring database credentials and API keys for the Norda Biznes Partner platform. Following these guidelines is **mandatory** to prevent security vulnerabilities and protect sensitive data.
### Security Vulnerability: CWE-798
**CWE-798: Use of Hard-coded Credentials** is a critical security vulnerability that occurs when passwords, API keys, or other sensitive credentials are embedded directly in source code. This practice:
- ❌ Exposes credentials if repository becomes public
- ❌ Makes credential rotation extremely difficult
- ❌ Violates compliance frameworks (OWASP, PCI-DSS, SOC 2)
- ❌ Creates audit trail of credentials in Git history
- ❌ Allows unauthorized access if code is compromised
**ALL credentials MUST be stored in environment variables, NEVER in source code.**
---
## Database Credentials Management
### Principles
1. **Never commit credentials to Git** - Use `.env` files (already in `.gitignore`)
2. **Use environment variables** - All scripts read from `DATABASE_URL` or `PGPASSWORD`
3. **Safe fallback values** - Default values use `CHANGE_ME` placeholder (will fail fast)
4. **Fail fast** - Scripts exit with clear error if credentials missing
5. **Separate dev/prod** - Different credentials for each environment
### Credential Storage Locations
| Environment | Location | User | Access |
|-------------|----------|------|--------|
| **Development** | `.env` in project root | Developer | Local only |
| **Production** | `/var/www/nordabiznes/.env` | www-data | Server only |
---
## Environment Variables Reference
### Required Variables
| Variable | Purpose | Used By | Example |
|----------|---------|---------|---------|
| `DATABASE_URL` | PostgreSQL connection string | Python scripts | `postgresql://user:pass@host:port/db` |
| `PGPASSWORD` | PostgreSQL password | Shell scripts (psql, pg_dump) | `your_secure_password` |
| `SECRET_KEY` | Flask session encryption | Flask app | Random string (64+ chars) |
| `GOOGLE_GEMINI_API_KEY` | Gemini AI chat | AI features | `AIzaSy...` |
| `GOOGLE_PAGESPEED_API_KEY` | SEO audits | Admin panel | `AIzaSy...` |
| `GOOGLE_PLACES_API_KEY` | Google Business Profile | GBP audits | `AIzaSy...` |
| `BRAVE_SEARCH_API_KEY` | News monitoring | News search | `BSA...` |
### Optional Variables
| Variable | Purpose | Default | Example |
|----------|---------|---------|---------|
| `FLASK_ENV` | Flask environment mode | `production` | `development` |
| `PORT` | Flask server port | `5000` | `5001` |
| `HOST` | Flask server host | `0.0.0.0` | `127.0.0.1` |
| `MAIL_SERVER` | Email SMTP server | None | `smtp.gmail.com` |
| `MAIL_USERNAME` | Email account | None | `noreply@example.com` |
| `MAIL_PASSWORD` | Email password/app password | None | `your_app_password` |
---
## Development Environment Setup
### Step 1: Create `.env` File
```bash
# In project root directory
cp .env.example .env
```
### Step 2: Configure Database Credentials
Edit `.env` and set your local database credentials:
```bash
# Development PostgreSQL (Docker)
DATABASE_URL=postgresql://nordabiz_user:nordabiz_password@localhost:5433/nordabiz
# Flask Configuration
SECRET_KEY=your-randomly-generated-secret-key-change-this
FLASK_ENV=development
PORT=5000
# API Keys (get from Google Cloud Console)
GOOGLE_GEMINI_API_KEY=your_gemini_key_here
GOOGLE_PAGESPEED_API_KEY=your_pagespeed_key_here
GOOGLE_PLACES_API_KEY=your_places_key_here
```
### Step 3: Start Local Database
```bash
# Start PostgreSQL in Docker
docker compose up -d
# Verify database is running
docker ps | grep postgres
```
### Step 4: Verify Configuration
```bash
# Test Python scripts can read DATABASE_URL
python3 -c "import os; print('DATABASE_URL:', os.getenv('DATABASE_URL', 'NOT SET'))"
# Expected output:
# DATABASE_URL: postgresql://nordabiz_user:nordabiz_password@localhost:5433/nordabiz
```
### Step 5: Run Application
```bash
# Activate virtual environment
source venv/bin/activate
# Run Flask application
python3 app.py
```
---
## Production Environment Setup
### Step 1: SSH to Production Server
```bash
ssh maciejpi@57.128.200.27
```
**IMPORTANT:** Always SSH as `maciejpi`, NEVER as root!
### Step 2: Configure Production `.env`
```bash
# Navigate to application directory
cd /var/www/nordabiznes
# Edit .env file (use sudo if needed)
sudo -u www-data nano .env
```
### Step 3: Set Production Credentials
```bash
# Production PostgreSQL (same server — OVH VPS 57.128.200.27)
DATABASE_URL=postgresql://nordabiz_app:YOUR_PRODUCTION_PASSWORD@127.0.0.1:5432/nordabiz
# Flask Configuration
SECRET_KEY=your-production-secret-key-64-random-characters-minimum
FLASK_ENV=production
PORT=5000
HOST=0.0.0.0
# API Keys (production keys from Google Cloud)
GOOGLE_GEMINI_API_KEY=your_production_gemini_key
GOOGLE_PAGESPEED_API_KEY=your_production_pagespeed_key
GOOGLE_PLACES_API_KEY=your_production_places_key
```
### Step 4: Set File Permissions
```bash
# Ensure .env is readable only by root (OVH VPS — .env is root-owned)
sudo chown root:root /var/www/nordabiznes/.env
sudo chmod 600 /var/www/nordabiznes/.env
# Verify permissions
ls -la /var/www/nordabiznes/.env
# Expected: -rw------- 1 root root
```
### Step 5: Restart Application
```bash
sudo systemctl restart nordabiznes
sudo systemctl status nordabiznes
```
---
## Shell Script Configuration
### Problem: Shell Scripts Cannot Read `.env` Files
Shell scripts (like `view_maturity_results.sh`) **cannot automatically read** `.env` files. You must set `PGPASSWORD` manually in your shell session.
### Solution 1: Export in Shell Session (Temporary)
```bash
# Set PGPASSWORD for current shell session
export PGPASSWORD='your_database_password'
# Run script
./view_maturity_results.sh
# Unset after use (optional, for security)
unset PGPASSWORD
```
### Solution 2: Inline Environment Variable (One-time)
```bash
# Set PGPASSWORD only for this command
PGPASSWORD='your_database_password' ./view_maturity_results.sh
```
### Solution 3: Use `.pgpass` File (Recommended for Production)
The `.pgpass` file allows PostgreSQL tools to authenticate without environment variables.
**Step 1: Create `.pgpass` file**
```bash
# Create file in home directory
nano ~/.pgpass
```
**Step 2: Add credentials (one line per database)**
```
# Format: hostname:port:database:username:password
57.128.200.27:5432:nordabiz:nordabiz_app:your_production_password
localhost:5433:nordabiz:nordabiz_user:nordabiz_password
```
**Step 3: Set correct permissions (REQUIRED)**
```bash
chmod 600 ~/.pgpass
# Verify
ls -la ~/.pgpass
# Expected: -rw------- 1 your_user your_group
```
**Step 4: Run script (no PGPASSWORD needed)**
```bash
./view_maturity_results.sh
# PostgreSQL will automatically read credentials from .pgpass
```
### Script Validation
All shell scripts now validate that `PGPASSWORD` is set before execution:
```bash
# Scripts check for PGPASSWORD at start
if [ -z "$PGPASSWORD" ]; then
echo "ERROR: PGPASSWORD environment variable is not set"
echo "Usage: PGPASSWORD='your_password' $0"
exit 1
fi
```
**If you see this error**, set PGPASSWORD using one of the methods above.
---
## Security Best Practices
### 1. Never Hardcode Credentials
**❌ WRONG - Hardcoded password in Python:**
```python
# NEVER DO THIS
DATABASE_URL = 'postgresql://user:MyPassword123@localhost/db'
```
**✅ CORRECT - Environment variable with safe fallback:**
```python
# Always use environment variables
DATABASE_URL = os.getenv('DATABASE_URL', 'postgresql://user:CHANGE_ME@localhost/db')
```
### 2. Never Hardcode Credentials in Shell Scripts
**❌ WRONG - Hardcoded PGPASSWORD:**
```bash
# NEVER DO THIS
PGPASSWORD='MyPassword123' psql -h localhost -U myuser -d mydb
```
**✅ CORRECT - Use environment variable:**
```bash
# Validate variable is set
if [ -z "$PGPASSWORD" ]; then
echo "ERROR: PGPASSWORD not set"
exit 1
fi
# Use variable
psql -h localhost -U myuser -d mydb
```
### 3. Keep `.env` Out of Version Control
The `.env` file is already in `.gitignore`. **NEVER remove it from `.gitignore`.**
```bash
# Verify .env is ignored
git status | grep .env
# Should show nothing (file is ignored)
```
### 4. Use `.env.example` as Template
The `.env.example` file is committed to Git and serves as a template. It contains:
- ✅ Variable names and structure
- ✅ Comments explaining each variable
- ✅ Example/placeholder values (not real credentials)
- ❌ NO real passwords or API keys
### 5. Rotate Credentials if Compromised
If credentials are accidentally committed to Git:
1. **Immediately change the password/API key** in database/service
2. Update `.env` files in all environments
3. Restart applications
4. Consider the old credential compromised (attackers may have Git history)
**Git history cleanup (advanced, use with caution):**
```bash
# Option 1: BFG Repo-Cleaner (recommended)
# https://rtyley.github.io/bfg-repo-cleaner/
# Option 2: git filter-branch (complex)
# Not recommended unless you know what you're doing
```
### 6. Use Strong Credentials
- **Database passwords:** Minimum 16 characters, mix of letters/numbers/symbols
- **Flask SECRET_KEY:** Minimum 64 random characters
- **API Keys:** Use keys from official provider (Google Cloud Console)
**Generate random SECRET_KEY:**
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
```
### 7. Principle of Least Privilege
- **Development:** Use separate database user with limited permissions
- **Production:** Use `nordabiz_app` user (not postgres superuser)
- **API Keys:** Enable only required APIs in Google Cloud Console
---
## Verification and Testing
### Pre-Deployment Checklist
Before deploying any code, run these verification commands:
```bash
# 1. Check for hardcoded passwords in Python files
grep -r "NordaBiz2025Secure" --include="*.py" .
# Expected: No results (or only in docs/)
# 2. Check for hardcoded PGPASSWORD in shell scripts
grep -r "PGPASSWORD=" --include="*.sh" .
# Expected: No results (or only validation checks)
# 3. Check for hardcoded DATABASE_URL with passwords
grep -r "postgresql://.*:.*@" --include="*.py" . | grep -v "CHANGE_ME" | grep -v ".example"
# Expected: No results (or only safe placeholders)
# 4. Verify .env is in .gitignore
git check-ignore .env
# Expected: .env (file is ignored)
# 5. Check what would be committed
git status
# Expected: .env should NOT appear in list
```
### Testing Environment Variable Loading
**Test Python scripts:**
```bash
# Unset DATABASE_URL to test fallback
unset DATABASE_URL
python3 -c "from database import get_database_url; print(get_database_url())"
# Expected: postgresql://user:CHANGE_ME@127.0.0.1:5432/nordabiz (safe fallback)
# Set DATABASE_URL to test loading
export DATABASE_URL='postgresql://test:test@localhost:5432/testdb'
python3 -c "from database import get_database_url; print(get_database_url())"
# Expected: postgresql://test:test@localhost:5432/testdb (from environment)
```
**Test shell scripts:**
```bash
# Unset PGPASSWORD to test validation
unset PGPASSWORD
./view_maturity_results.sh
# Expected: ERROR message about PGPASSWORD not set
# Set PGPASSWORD to test execution
export PGPASSWORD='test_password'
./view_maturity_results.sh
# Expected: Script runs (may fail if credentials wrong, but validation passed)
```
### Continuous Monitoring
Add these checks to your deployment pipeline:
```bash
# In CI/CD or pre-commit hook
if grep -r "NordaBiz2025Secure" --include="*.py" --include="*.sh" .; then
echo "ERROR: Found hardcoded credentials in code"
exit 1
fi
```
---
## Troubleshooting
### Issue: "PGPASSWORD not set" error in shell script
**Symptom:**
```
ERROR: PGPASSWORD environment variable is not set
```
**Solution:**
```bash
# Set PGPASSWORD before running script
export PGPASSWORD='your_database_password'
./your_script.sh
```
### Issue: "Password authentication failed" in Python script
**Symptom:**
```
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) FATAL: password authentication failed
```
**Diagnosis:**
```bash
# Check if DATABASE_URL is set
echo $DATABASE_URL
# Check if DATABASE_URL in .env is correct
cat .env | grep DATABASE_URL
```
**Solution:**
1. Verify password is correct in `.env`
2. Ensure `.env` file exists and is readable
3. Check database user exists: `psql -U postgres -c "\du"`
### Issue: Script uses 'CHANGE_ME' placeholder password
**Symptom:**
```
sqlalchemy.exc.OperationalError: FATAL: password authentication failed for user "nordabiz_app"
```
**Cause:** DATABASE_URL environment variable is not set, script is using safe fallback.
**Solution:**
```bash
# Check if .env file exists
ls -la .env
# Check if DATABASE_URL is in .env
grep DATABASE_URL .env
# Ensure you're loading .env (Flask should do this automatically)
# For standalone scripts, may need to load manually:
python3 -c "from dotenv import load_dotenv; load_dotenv(); import os; print(os.getenv('DATABASE_URL'))"
```
### Issue: ".env file not found" on production
**Symptom:**
Application can't find credentials, using fallback values.
**Diagnosis:**
```bash
# Check if .env exists in application directory
ls -la /var/www/nordabiznes/.env
# Check file ownership and permissions
ls -la /var/www/nordabiznes/.env
# Expected: -rw------- 1 www-data www-data
```
**Solution:**
```bash
# Create .env from template
cd /var/www/nordabiznes
sudo -u www-data cp .env.example .env
sudo -u www-data nano .env # Edit with production credentials
# Set correct permissions
sudo chown www-data:www-data .env
sudo chmod 600 .env
# Restart application
sudo systemctl restart nordabiznes
```
---
## Incident Response
### If Credentials Are Committed to Git
**CRITICAL: Follow these steps immediately**
1. **Change the compromised credential**
```bash
# For database password
psql -U postgres -d nordabiz -c "ALTER USER nordabiz_app WITH PASSWORD 'new_secure_password';"
# For API keys
# Disable old key in Google Cloud Console and create new one
```
2. **Update `.env` files in all environments**
```bash
# Development
nano .env # Update DATABASE_URL with new password
# Production (OVH VPS)
ssh maciejpi@57.128.200.27
cd /var/www/nordabiznes
sudo nano .env # Update DATABASE_URL with new password (.env is root-owned)
sudo systemctl restart nordabiznes
```
3. **Remove credential from Git history (optional, advanced)**
```bash
# WARNING: This rewrites Git history and requires force push
# Coordinate with team before doing this
# Using BFG Repo-Cleaner (recommended)
java -jar bfg.jar --replace-text passwords.txt nordabiz.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force
```
4. **Document the incident**
- Create incident report in `docs/INCIDENT_REPORT_YYYYMMDD.md`
- Document what was exposed, when, and remediation steps
- Review with team to prevent future incidents
5. **Review access logs**
```bash
# Check PostgreSQL logs for unauthorized access
sudo tail -100 /var/log/postgresql/postgresql-14-main.log
# Check Flask application logs
sudo journalctl -u nordabiznes -n 100
```
### Emergency Contacts
| Role | Contact | Responsibility |
|------|---------|----------------|
| System Administrator | maciejpi@inpi.local | Infrastructure, database |
| Lead Developer | [Your contact] | Application code |
| Security Officer | [Contact if applicable] | Security incidents |
---
## Additional Resources
### Official Documentation
- PostgreSQL Authentication: https://www.postgresql.org/docs/current/auth-methods.html
- PostgreSQL .pgpass file: https://www.postgresql.org/docs/current/libpq-pgpass.html
- Flask Configuration: https://flask.palletsprojects.com/en/2.3.x/config/
- python-dotenv: https://github.com/theskumar/python-dotenv
### Security Standards
- CWE-798: Use of Hard-coded Credentials: https://cwe.mitre.org/data/definitions/798.html
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- OWASP Secrets Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
### Internal Documentation
- Project README: `README.md`
- Developer Guide: `CLAUDE.md`
- Environment Configuration: `.env.example`
- Architecture Documentation: `docs/architecture/`
---
**Document Version:** 1.0
**Last Reviewed:** 2026-01-10
**Next Review:** 2026-04-10 (quarterly)