From d0a2273b71c78700c26f863f106ea5b6087127e1 Mon Sep 17 00:00:00 2001 From: ImpulsiveFPS Date: Tue, 3 Feb 2026 12:09:13 +0100 Subject: [PATCH] Initial LemonSec deployment Features: - Traefik reverse proxy with automatic SSL (Let's Encrypt) - Authelia SSO and 2FA authentication - CrowdSec intrusion detection/prevention - AdGuard Home DNS - TrueNAS Nextcloud routing configured - Portainer Git Repository deployment ready Security: - Cloudflare integration with strict SSL - Rate limiting and security headers - Network segmentation (external/internal) - Automatic threat blocking --- .env.example | 59 +++++ .gitignore | 53 +++++ GIT-REPO-SETUP.md | 239 ++++++++++++++++++++ MIGRATE-FROM-NPM.md | 254 +++++++++++++++++++++ PORTAINER-DEPLOY.md | 299 ++++++++++++++++++++++++ QUICKSTART.md | 249 ++++++++++++++++++++ README-DEPLOY.md | 162 +++++++++++++ README.md | 312 ++++++++++++++++++++++++++ SETUP-TRUENAS-NEXTCLOUD.md | 244 ++++++++++++++++++++ SETUP.md | 215 ++++++++++++++++++ SUMMARY.md | 110 +++++++++ authelia/configuration.yml | 203 +++++++++++++++++ authelia/users_database.yml | 19 ++ crowdsec/acquis.yaml | 24 ++ docker-compose.external.yml | 85 +++++++ docker-compose.override.yml.example | 110 +++++++++ docker-compose.yml | 301 +++++++++++++++++++++++++ docs/CLOUDFLARE.md | 173 ++++++++++++++ docs/SECURITY.md | 278 +++++++++++++++++++++++ docs/TAILSCALE.md | 173 ++++++++++++++ docs/TRUENAS-NEXTCLOUD.md | 280 +++++++++++++++++++++++ examples/internal-service-compose.yml | 32 +++ examples/nextcloud-compose.yml | 61 +++++ examples/vaultwarden-compose.yml | 57 +++++ monitoring/loki-config.yml | 40 ++++ monitoring/prometheus.yml | 26 +++ monitoring/promtail-config.yml | 35 +++ setup.ps1 | 93 ++++++++ stack.env | 56 +++++ traefik/dynamic/middlewares.yml | 128 +++++++++++ traefik/dynamic/tls.yml | 44 ++++ traefik/traefik.yml | 163 ++++++++++++++ 32 files changed, 4577 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 GIT-REPO-SETUP.md create mode 100644 MIGRATE-FROM-NPM.md create mode 100644 PORTAINER-DEPLOY.md create mode 100644 QUICKSTART.md create mode 100644 README-DEPLOY.md create mode 100644 README.md create mode 100644 SETUP-TRUENAS-NEXTCLOUD.md create mode 100644 SETUP.md create mode 100644 SUMMARY.md create mode 100644 authelia/configuration.yml create mode 100644 authelia/users_database.yml create mode 100644 crowdsec/acquis.yaml create mode 100644 docker-compose.external.yml create mode 100644 docker-compose.override.yml.example create mode 100644 docker-compose.yml create mode 100644 docs/CLOUDFLARE.md create mode 100644 docs/SECURITY.md create mode 100644 docs/TAILSCALE.md create mode 100644 docs/TRUENAS-NEXTCLOUD.md create mode 100644 examples/internal-service-compose.yml create mode 100644 examples/nextcloud-compose.yml create mode 100644 examples/vaultwarden-compose.yml create mode 100644 monitoring/loki-config.yml create mode 100644 monitoring/prometheus.yml create mode 100644 monitoring/promtail-config.yml create mode 100644 setup.ps1 create mode 100644 stack.env create mode 100644 traefik/dynamic/middlewares.yml create mode 100644 traefik/dynamic/tls.yml create mode 100644 traefik/traefik.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9570935 --- /dev/null +++ b/.env.example @@ -0,0 +1,59 @@ +# ============================================================================ +# LEMONSEC - Environment Configuration +# Copy this file to .env and fill in your values +# ============================================================================ + +# ----------------------------------------------------------------------------- +# Cloudflare DNS Configuration +# ----------------------------------------------------------------------------- +# Your Cloudflare account email +CF_API_EMAIL=your-email@example.com + +# Cloudflare Global API Key (from https://dash.cloudflare.com/profile/api-tokens) +# OR use DNS API Token (more secure) +CF_API_KEY=your-cloudflare-global-api-key +# CF_DNS_API_TOKEN=your-cloudflare-dns-api-token + +# ----------------------------------------------------------------------------- +# Timezone +# ----------------------------------------------------------------------------- +TZ=Europe/Stockholm + +# ----------------------------------------------------------------------------- +# CrowdSec API Key +# Generate after first CrowdSec start: docker exec crowdsec cscli bouncers add traefik-bouncer +CROWDSEC_API_KEY=your-crowdsec-api-key-here + +# ----------------------------------------------------------------------------- +# Authelia Secrets +# Generate these with: openssl rand -hex 32 +# ----------------------------------------------------------------------------- +AUTHELIA_JWT_SECRET=change-me-32-characters-min +AUTHELIA_SESSION_SECRET=change-me-32-characters-min +AUTHELIA_STORAGE_KEY=change-me-32-characters-min + +# ----------------------------------------------------------------------------- +# Grafana +# ----------------------------------------------------------------------------- +GRAFANA_ADMIN_USER=admin +GRAFANA_ADMIN_PASSWORD=change-me-strong-password +GRAFANA_OAUTH_SECRET=generate-with-authelia + +# ----------------------------------------------------------------------------- +# Tailscale Configuration +# Your Tailscale IP for internal routing +# Find with: tailscale ip -4 +# ----------------------------------------------------------------------------- +TAILSCALE_IP=100.x.x.x + +# ----------------------------------------------------------------------------- +# TrueNAS / External Services +# ----------------------------------------------------------------------------- +TRUENAS_IP=192.168.x.x # Your TrueNAS VM IP +TRUENAS_NEXTCLOUD_PORT=9001 # Port where TrueNAS Nextcloud is exposed + +# ----------------------------------------------------------------------------- +# External Services (add your own) +# ----------------------------------------------------------------------------- +# NEXTCLOUD_MYSQL_PASSWORD=... +# VAULTWARDEN_ADMIN_TOKEN=... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c69bbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# Environment files (local development only) +.env +.env.local +.env.production + +# Keep stack.env as template for Portainer +# stack.env is intentionally NOT ignored + +# Secrets +secrets/*.txt +secrets/*.key +secrets/*.pem + +# Generated certificates +letsencrypt/ +*.crt +*.key +*.pem + +# Data directories +*/data/ +*/db/ +*.sqlite3 + +# Backups +*.tar.gz +*.zip +backup/ +backups/ + +# Logs +*.log +logs/ + +# Runtime files +.pid +.sock + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Docker volumes that might be mounted +adguard-work/ +adguard-conf/ +uptime-kuma-data/ diff --git a/GIT-REPO-SETUP.md b/GIT-REPO-SETUP.md new file mode 100644 index 0000000..671fbab --- /dev/null +++ b/GIT-REPO-SETUP.md @@ -0,0 +1,239 @@ +# Git Repository Setup for Portainer + +This guide shows how to push LemonSec to your Git server for Portainer deployment. + +## Files to Commit + +These files should be in your Git repository: + +``` +LemonSec/ +├── docker-compose.yml ✅ Main stack configuration +├── stack.env ✅ Environment variable template +├── .gitignore ✅ Git ignore rules +├── README.md ✅ Main documentation +├── SUMMARY.md ✅ Quick reference +├── PORTAINER-DEPLOY.md ✅ Portainer deployment guide +├── SETUP-TRUENAS-NEXTCLOUD.md ✅ TrueNAS specific guide +├── MIGRATE-FROM-NPM.md ✅ NPM migration guide +├── SETUP.md ✅ General setup guide +│ +├── traefik/ ✅ Traefik configuration +│ ├── traefik.yml +│ └── dynamic/ +│ ├── middlewares.yml +│ └── tls.yml +│ +├── authelia/ ✅ Authelia configuration +│ ├── configuration.yml +│ └── users_database.yml ✅ (Add family users here) +│ +├── crowdsec/ ✅ CrowdSec configuration +│ └── acquis.yaml +│ +├── docs/ ✅ Documentation +│ ├── CLOUDFLARE.md +│ ├── TAILSCALE.md +│ └── SECURITY.md +│ +├── examples/ ✅ Service examples +│ ├── nextcloud-compose.yml +│ ├── vaultwarden-compose.yml +│ └── internal-service-compose.yml +│ +└── monitoring/ ✅ Monitoring configs + ├── prometheus.yml + ├── loki-config.yml + └── promtail-config.yml +``` + +## Files NOT to Commit + +These are in `.gitignore`: + +- `.env` - Contains your actual secrets +- `secrets/` directory - Secret files +- `*.log` - Log files +- `traefik/logs/` - Traefik logs +- `*.tar.gz` - Backup files + +## Step-by-Step Git Setup + +### 1. Initialize Repository + +```bash +cd LemonSec + +git init +``` + +### 2. Add Your Git Server + +```bash +git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git +``` + +### 3. Configure Git (if needed) + +```bash +git config user.name "Your Name" +git config user.email "your.email@example.com" +``` + +### 4. Add and Commit Files + +```bash +# Add all files +git add . + +# Commit +git commit -m "Initial LemonSec deployment + +- Traefik reverse proxy with Cloudflare SSL +- Authelia SSO and 2FA +- CrowdSec intrusion detection +- AdGuard Home DNS +- TrueNAS Nextcloud routing +" +``` + +### 5. Push to Server + +```bash +# For main branch +git push -u origin main + +# Or if your default is master +git push -u origin master +``` + +### 6. Verify + +```bash +# Check remote +git remote -v + +# Should show: +# origin https://git.lemonlink.eu/impulsivefps/LemonSec.git (fetch) +# origin https://git.lemonlink.eu/impulsivefps/LemonSec.git (push) + +# Check status +git status +# Should show: "nothing to commit, working tree clean" +``` + +## Updating the Repository + +After making changes: + +```bash +# Make changes to files +nano authelia/users_database.yml # Add family member + +# Commit +git add authelia/users_database.yml +git commit -m "Add family member to Authelia" + +# Push +git push + +# In Portainer: Pull and redeploy stack +``` + +## Repository URL for Portainer + +Use this URL in Portainer: + +``` +https://git.lemonlink.eu/impulsivefps/LemonSec +``` + +### If Repository is Private + +If your Git server requires authentication: + +1. **Option A: Deploy Key** (Recommended) + - Generate SSH key pair + - Add public key to Git repo as deploy key + - Use SSH URL: `git@git.lemonlink.eu:impulsivefps/LemonSec.git` + +2. **Option B: Personal Access Token** + - Create token in Git settings + - Use HTTPS with token: `https://token@git.lemonlink.eu/impulsivefps/LemonSec` + +3. **Option C: Portainer Git Credentials** + - In Portainer stack settings + - Enable authentication + - Enter username/password + +## Testing Git Access + +From your Proxmox VM: + +```bash +# Test HTTPS access +git ls-remote https://git.lemonlink.eu/impulsivefps/LemonSec + +# Should show refs without errors +``` + +## Troubleshooting + +### "repository not found" +- Verify URL is correct +- Check repository exists on Git server +- Confirm permissions + +### "Authentication failed" +- Check credentials +- Verify deploy key is added (if using SSH) +- Try accessing in browser first + +### "Updates were rejected" +```bash +# Pull first +git pull origin main + +# Then push +git push +``` + +### Large files / Binary files +If you accidentally committed secrets: +```bash +# Remove from history (be careful!) +git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch .env' HEAD + +# Force push +git push --force +``` + +## Branch Strategy + +For simple deployments, use `main` or `master`: + +```bash +# Check current branch +git branch + +# Create and switch to main if needed +git checkout -b main +git push -u origin main +``` + +For advanced setups, you might want: +- `main` - Production +- `develop` - Testing +- `feature/*` - New services + +## Automated Updates + +Set up webhook (if your Git server supports it): +1. Go to Git repo settings +2. Add webhook URL: `http://portainer:9000/api/stacks/{stack_id}/git/redeploy` +3. On push, Portainer auto-redeploys + +Or use Portainer's polling: +- Stacks → lemonsec → Git settings +- Enable automatic updates +- Set interval (e.g., 5 minutes) diff --git a/MIGRATE-FROM-NPM.md b/MIGRATE-FROM-NPM.md new file mode 100644 index 0000000..31684c0 --- /dev/null +++ b/MIGRATE-FROM-NPM.md @@ -0,0 +1,254 @@ +# Migrating from Nginx Proxy Manager (NPM) + +## Concept Mapping + +| NPM Feature | Traefik Equivalent | +|-------------|-------------------| +| Proxy Hosts | Routers + Services | +| SSL Certificates | certresolver (automatic) | +| Custom Locations | Middlewares | +| Access Lists | Authelia / Basic Auth | +| 404 Page | Custom error middleware | +| Redirection Hosts | Redirect middleware | +| Streams | TCP Routers | + +## Migration Steps + +### 1. Document Current NPM Setup + +In NPM UI, go through each Proxy Host and note: +- Domain names +- Forward hostname/IP and port +- Scheme (http/https) +- Custom locations +- Access lists +- SSL settings +- Advanced configuration + +### 2. Export NPM Config (Optional Backup) + +NPM → **Settings** → **Backup** → **Download** + +This creates a JSON backup of all hosts. + +### 3. Create docker-compose.override.yml + +This is where your migrated hosts go: + +```yaml +version: "3.8" + +networks: + services: + external: true + +services: + # ======================================== + # MIGRATED FROM NPM + # ======================================== + + # Example: Migrating "nextcloud.lemonlink.eu" + # NPM Settings: + # - Domain: nextcloud.lemonlink.eu + # - Forward: 192.168.1.100:9001 + # - Scheme: http + # - Block Common Exploits: yes + # - SSL: yes (LE) + + nextcloud-migration: + image: alpine:latest + container_name: npm-migration-nextcloud + restart: "no" + command: "echo 'Migrated from NPM'" + networks: + - services + labels: + # Enable Traefik + - "traefik.enable=true" + + # Router (like Proxy Host) + - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.lemonlink.eu`)" + - "traefik.http.routers.nextcloud.entrypoints=websecure" + - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" + + # Service (Forward to) + - "traefik.http.services.nextcloud.loadbalancer.server.url=http://192.168.1.100:9001" + + # Middlewares (Access Lists, Security) + - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file" + + # Add more migrations here... + # service2-migration: + # ... +``` + +### 4. Common NPM → Traefik Patterns + +#### Pattern 1: Basic Proxy Host +``` +NPM: + Domain: app.lemonlink.eu + Forward: 192.168.1.10:8080 + SSL: yes + +Traefik: + labels: + - "traefik.enable=true" + - "traefik.http.routers.app.rule=Host(`app.lemonlink.eu`)" + - "traefik.http.routers.app.entrypoints=websecure" + - "traefik.http.routers.app.tls.certresolver=letsencrypt" + - "traefik.http.services.app.loadbalancer.server.url=http://192.168.1.10:8080" + - "traefik.http.routers.app.middlewares=security-headers@file,rate-limit@file" +``` + +#### Pattern 2: Access List (Basic Auth) +``` +NPM: + Access List: Family + Users: user1 / pass1, user2 / pass2 + +Traefik: + # In docker-compose.override.yml middlewares section + # Or use Authelia (recommended) + + # Option A: Basic Auth (simple) + labels: + - "traefik.http.routers.app.middlewares=app-basicauth" + - "traefik.http.middlewares.app-basicauth.basicauth.users=user1:$$apr1$$..." + + # Option B: Authelia (SSO, recommended) + labels: + - "traefik.http.routers.app.middlewares=authelia@docker" +``` + +#### Pattern 3: Custom Location +``` +NPM: + Location: /api + Forward: 192.168.1.10:8080/api + +Traefik: + # Use PathPrefix rule + labels: + - "traefik.http.routers.app-api.rule=Host(`app.lemonlink.eu`) && PathPrefix(`/api`)" + - "traefik.http.routers.app-api.service=api-service" + - "traefik.http.services.api-service.loadbalancer.server.url=http://192.168.1.10:8080" +``` + +#### Pattern 4: Custom Nginx Config +``` +NPM Advanced: + proxy_set_header X-Custom-Header value; + client_max_body_size 10G; + +Traefik: + # Add to dynamic/middlewares.yml or inline + labels: + - "traefik.http.middlewares.custom-headers.headers.customRequestHeaders.X-Custom-Header=value" + - "traefik.http.routers.app.middlewares=custom-headers" + + # For body size, use buffering: + - "traefik.http.services.app.loadbalancer.responseforwarding.flushinterval=100ms" +``` + +### 5. Gradual Migration Strategy + +Don't migrate everything at once: + +**Week 1: Setup LemonSec** +- Deploy Traefik + Authelia + CrowdSec +- Test with one non-critical service +- Keep NPM running + +**Week 2: Migrate Low-Risk Services** +- Move internal services first +- Test thoroughly +- Keep Nextcloud on NPM for now + +**Week 3: Migrate Nextcloud** +- Schedule downtime +- Update TrueNAS Nextcloud config +- Test with family +- Keep NPM as backup + +**Week 4: Decommission NPM** +- After 1 week stable +- Stop NPM container +- Remove port forwards to NPM +- Keep config backup + +### 6. Parallel Running + +You can run both during migration: + +``` +Internet + ↓ +Router (port 443) + ↓ + ├─► NPM (port 444) ──► Some services + └─► Traefik (port 443) ──► New services +``` + +Change router port forward to test: +- Port 443 → Traefik +- Port 444 → NPM (backup access) + +### 7. Testing Checklist + +For each migrated service: +- [ ] HTTP redirects to HTTPS +- [ ] SSL certificate valid +- [ ] Can login +- [ ] All pages load +- [ ] File uploads work +- [ ] Mobile apps connect +- [ ] No console errors + +### 8. Rollback Plan + +If something breaks: + +```bash +# Stop Traefik +docker-compose -f docker-compose.yml -f docker-compose.override.yml down + +# Restart NPM +docker start npm + +# Revert router port forward to NPM +# Access via: https://old-domain:444 (if configured) +``` + +Keep NPM container stopped but available: +```bash +docker stop npm +docker rename npm npm-backup +# Don't delete until migration complete +``` + +## NPM Features Not in Traefik + +| NPM Feature | Traefik Alternative | +|-------------|---------------------| +| Nice Web UI | Traefik Dashboard + Portainer | +| One-click SSL | Automatic via certresolver | +| Built-in auth | Authelia (more powerful) | +| Redirection UI | YAML config | +| Access Logs | JSON logs in traefik/logs/ | + +## Benefits of Migrating + +1. **Declarative**: All config in Git-trackable files +2. **Automatic**: SSL certificates without clicks +3. **Integrated**: Built-in CrowdSec, Authelia +4. **Fast**: Better performance than NPM +5. **Flexible**: More routing options +6. **Modern**: Active development, Traefik 3.x + +## Need Help? + +- Check `examples/` for common patterns +- See `docs/TRUENAS-NEXTCLOUD.md` for Nextcloud specifically +- Review `QUICKSTART.md` for fresh setup +- Traefik docs: https://doc.traefik.io/ diff --git a/PORTAINER-DEPLOY.md b/PORTAINER-DEPLOY.md new file mode 100644 index 0000000..8c2d2de --- /dev/null +++ b/PORTAINER-DEPLOY.md @@ -0,0 +1,299 @@ +# Deploy LemonSec via Portainer Git Repository + +## Overview + +This guide shows how to deploy LemonSec using Portainer's **Git Repository** feature. This is the cleanest deployment method - no manual file copying, just point Portainer to your Git repo! + +## Prerequisites + +- [ ] Portainer installed on Proxmox VM +- [ ] Git repository at `https://git.lemonlink.eu/impulsivefps/LemonSec` +- [ ] Cloudflare account with `lemonlink.eu` domain +- [ ] TrueNAS Scale running Nextcloud + +## Step 1: Prepare Git Repository + +### On your local machine (where you created the files): + +```bash +# Initialize git repo (if not already) +cd LemonSec +git init +git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git + +# Add all files +git add . +git commit -m "Initial LemonSec deployment" + +# Push to your Git server +git push -u origin main +# or git push -u origin master +``` + +### Files that should be in the repo: +``` +LemonSec/ +├── docker-compose.yml ✅ Main stack +├── stack.env ✅ Environment template +├── traefik/ +│ ├── traefik.yml ✅ Static config +│ └── dynamic/ +│ ├── middlewares.yml ✅ Security middlewares +│ └── tls.yml ✅ TLS settings +├── authelia/ +│ ├── configuration.yml ✅ Authelia config +│ └── users_database.yml ✅ Family users +├── crowdsec/ +│ └── acquis.yaml ✅ Log sources +└── docs/ ✅ Documentation +``` + +### Files to NOT commit (already in .gitignore): +- `.env` - Contains secrets +- `secrets/` directory +- Any `*.log` files +- Backup files + +## Step 2: Generate Secrets + +Before deploying, generate the Authelia secrets: + +### Option A: On Windows (PowerShell) +```powershell +$jwt = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) +Write-Host "AUTHELIA_JWT_SECRET: $jwt" + +$session = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) +Write-Host "AUTHELIA_SESSION_SECRET: $session" + +$storage = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) +Write-Host "AUTHELIA_STORAGE_KEY: $storage" +``` + +### Option B: On Linux/macOS +```bash +openssl rand -hex 32 # Run 3 times for each secret +echo "AUTHELIA_JWT_SECRET: $(openssl rand -hex 32)" +echo "AUTHELIA_SESSION_SECRET: $(openssl rand -hex 32)" +echo "AUTHELIA_STORAGE_KEY: $(openssl rand -hex 32)" +``` + +### Option C: In Portainer (after first failed deploy) +The stack will fail to start without secrets. You can generate them inside the Portainer host: +```bash +docker run --rm authelia/authelia:latest authelia crypto rand --length 64 --hex +docker run --rm alpine openssl rand -hex 32 +``` + +**Save these 3 secrets!** You'll need them in Step 4. + +## Step 3: Deploy in Portainer + +### 1. Open Portainer +Navigate to your Portainer UI: `http://your-proxmox-vm:9000` + +### 2. Create Stack from Git +- Click **Stacks** → **Add Stack** +- Select **Repository** (not Web editor) +- Fill in: + +| Field | Value | +|-------|-------| +| **Name** | `lemonsec` | +| **Repository URL** | `https://git.lemonlink.eu/impulsivefps/LemonSec` | +| **Repository Reference** | `refs/heads/main` (or `refs/heads/master`) | +| **Compose Path** | `docker-compose.yml` | +| **Authentication** | (only if repo is private) | + +### 3. Environment Variables +Click **Load variables from .env file** or enter manually: + +**Required Variables:** + +| Variable | Example Value | Description | +|----------|---------------|-------------| +| `CF_API_EMAIL` | `youremail@example.com` | Cloudflare account email | +| `CF_API_KEY` | `your-cloudflare-global-api-key` | From Cloudflare profile | +| `TRUENAS_IP` | `192.168.1.100` | TrueNAS Scale VM IP | +| `TRUENAS_NEXTCLOUD_PORT` | `9001` | Nextcloud port on TrueNAS | +| `AUTHELIA_JWT_SECRET` | `a1b2c3d4...` (64 hex chars) | Generated secret | +| `AUTHELIA_SESSION_SECRET` | `e5f6g7h8...` (64 hex chars) | Generated secret | +| `AUTHELIA_STORAGE_KEY` | `i9j0k1l2...` (64 hex chars) | Generated secret | + +**Optional Variables:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `TZ` | `Europe/Stockholm` | Timezone | +| `TAILSCALE_IP` | (empty) | For internal access | +| `CROWDSEC_API_KEY` | (empty) | Add after first deploy | + +### 4. Deploy the Stack +- Click **Deploy the stack** +- Portainer will pull from Git and start containers + +## Step 4: Initial Configuration + +### 1. Check Deployment Status +In Portainer: +- **Stacks** → **lemonsec** → Check all containers are running +- Click on individual containers to view logs + +### 2. Generate CrowdSec API Key +Once CrowdSec is running: +```bash +# In Portainer → Containers → crowdsec → Console +# Or SSH to host: +docker exec crowdsec cscli bouncers add traefik-bouncer + +# Copy the API key output +``` + +### 3. Update Environment Variables +- **Stacks** → **lemonsec** → **Editor** tab +- Add `CROWDSEC_API_KEY=your-key-here` +- Click **Update the stack** + +### 4. Configure TrueNAS Nextcloud +In TrueNAS Scale: +1. **Apps** → **Installed** → **Nextcloud** → **Edit** +2. **Environment Variables**: + ``` + NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu + OVERWRITEPROTOCOL=https + OVERWRITEHOST=cloud.lemonlink.eu + OVERWRITECLIURL=https://cloud.lemonlink.eu + TRUSTED_PROXIES=192.168.1.50 + ``` + (Replace 192.168.1.50 with your Proxmox VM IP) +3. **Save** + +### 5. Configure Cloudflare DNS +1. Login to [Cloudflare Dashboard](https://dash.cloudflare.com) +2. Add DNS records: + +| Type | Name | Target | Proxy | +|------|------|--------|-------| +| A | cloud | YOUR_PUBLIC_IP | 🟠 Proxied | +| A | auth | YOUR_PUBLIC_IP | 🟠 Proxied | +| A | *.local | YOUR_PUBLIC_IP | 🟠 Proxied | + +3. **SSL/TLS** → **Overview**: + - Set to **Full (strict)** + - Enable **Always Use HTTPS** + +## Step 5: Verify Everything Works + +### Test Commands +```bash +# From any machine: +curl -I https://cloud.lemonlink.eu +# Expected: HTTP/2 200 or redirect to login + +# Check Traefik dashboard (via Tailscale): +curl -k -I https://traefik.local.lemonlink.eu:8443 + +# Verify SSL certificate: +echo | openssl s_client -servername cloud.lemonlink.eu -connect cloud.lemonlink.eu:443 2>/dev/null | openssl x509 -noout -dates +``` + +### Web Access +- **Nextcloud**: `https://cloud.lemonlink.eu` ✅ +- **Authelia**: `https://auth.lemonlink.eu` ✅ + +## Step 6: Add Family to Authelia (Optional) + +If you enabled Authelia on Nextcloud: + +1. Generate password hash: +```bash +docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'FamilyPassword123!' +``` + +2. Edit `authelia/users_database.yml` in your Git repo +3. Push changes +4. In Portainer: **Stacks** → **lemonsec** → **Pull and redeploy** + +## Updating the Stack + +### Update from Git (new commits) +1. **Stacks** → **lemonsec** +2. Click **Pull and redeploy** +3. Portainer pulls latest changes and restarts + +### Update container images +1. **Stacks** → **lemonsec** → **Editor** +2. Click **Update the stack** (this pulls new images) + +Or manually: +```bash +# SSH to host +docker-compose -f /var/lib/docker/volumes/portainer_data/_data/compose/lemonsec/docker-compose.yml pull +docker-compose -f /var/lib/docker/volumes/portainer_data/_data/compose/lemonsec/docker-compose.yml up -d +``` + +## Troubleshooting + +### "CF_API_EMAIL not set" error +- Check all required environment variables are filled in Portainer +- Redeploy the stack + +### "Bad Gateway" for Nextcloud +- Verify `TRUENAS_IP` and `TRUENAS_NEXTCLOUD_PORT` are correct +- Test: `curl http://TRUENAS_IP:TRUENAS_NEXTCLOUD_PORT` from Proxmox VM + +### SSL Certificate issues +- Check Cloudflare API credentials +- Verify DNS records exist +- Check Traefik logs in Portainer + +### CrowdSec not blocking +- Verify `CROWDSEC_API_KEY` is set and correct +- Check CrowdSec logs for connection errors + +### Can't pull from Git +- Verify Git URL is accessible from Portainer host +- Check if repository requires authentication +- Try: `git ls-remote https://git.lemonlink.eu/impulsivefps/LemonSec` from host + +## Portainer Stack vs Docker Compose + +| Feature | Portainer Stacks | Docker Compose CLI | +|---------|------------------|-------------------| +| Deployment | Git/Web UI | Command line | +| Updates | One-click pull | Manual git pull | +| Environment | UI variables | .env file | +| Logs | Built-in UI | docker logs | +| Access control | Portainer RBAC | OS level | + +## Backup Strategy + +### Portainer Backup +Portainer automatically stores stack files in: +``` +/var/lib/docker/volumes/portainer_data/_data/compose/ +``` + +### Manual Backup +```bash +# Backup Portainer data +docker run --rm -v portainer_data:/data -v $(pwd):/backup alpine tar czf /backup/portainer-backup.tar.gz -C /data . + +# Backup LemonSec volumes +docker volume ls | grep lemonsec +docker run --rm -v lemonsec_traefik-certs:/certs -v $(pwd):/backup alpine tar czf /backup/traefik-certs.tar.gz -C /certs . +``` + +## Next Steps + +1. ✅ Verify Nextcloud is accessible +2. ✅ Test family member logins +3. ✅ Set up mobile apps +4. ✅ Enable monitoring (optional) +5. ✅ Add more services (Vaultwarden, etc.) + +## Support + +- **Traefik issues**: Check logs in Portainer → Containers → traefik → Logs +- **Authelia issues**: Check logs in Portainer → Containers → authelia → Logs +- **TrueNAS issues**: Check TrueNAS → Apps → Nextcloud → Logs diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..b2df2b6 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,249 @@ +# LemonSec Quick Start Guide + +## 🚀 Deployment Steps + +### Step 1: Prepare Your Server + +```bash +# On Proxmox (Debian/Ubuntu) +sudo apt update && sudo apt install -y docker.io docker-compose +sudo usermod -aG docker $USER +# Log out and back in +``` + +### Step 2: Configure Environment + +```bash +cd LemonSec + +# 1. Copy environment template +cp .env.example .env + +# 2. Edit with your details +nano .env +# Required: +# - CF_API_EMAIL (your Cloudflare email) +# - CF_API_KEY (from https://dash.cloudflare.com/profile/api-tokens) +# - TAILSCALE_IP (from `tailscale ip -4`) +``` + +### Step 3: Setup Authelia + +```bash +# Generate password hash for admin user +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 \ + --password 'YourSecurePassword123!' + +# Edit users database +nano authelia/users_database.yml +# Replace the password hash with the one you generated +``` + +### Step 4: Run Setup Script + +```powershell +# On Windows +.\setup.ps1 + +# On Linux (create first) +# bash setup.sh +``` + +### Step 5: Start Core Services + +```bash +# Start everything +docker-compose up -d + +# Check Traefik is working +docker-compose logs -f traefik + +# You should see "Configuration loaded from files..." and no errors +``` + +### Step 6: Configure CrowdSec + +```bash +# Generate API key for Traefik bouncer +docker-compose exec crowdsec cscli bouncers add traefik-bouncer + +# Copy the key and add to .env: +# CROWDSEC_API_KEY=your-key-here + +# Restart to apply +docker-compose up -d +``` + +### Step 7: Verify DNS + +In Cloudflare DNS, ensure you have: + +| Type | Name | Target | Proxy | +|------|------|--------|-------| +| A | @ | YOUR_IP | 🟠 | +| A | * | YOUR_IP | 🟠 | +| A | auth | YOUR_IP | 🟠 | + +### Step 8: Test Access + +```bash +# Test external (after DNS propagates) +curl -I https://auth.lemonlink.eu + +# Test internal (via Tailscale) +curl -k -I https://traefik.local.lemonlink.eu:8443 +``` + +## 📝 Adding Services + +### External Service (e.g., Nextcloud) + +```yaml +# In docker-compose.override.yml +services: + nextcloud: + image: nextcloud:latest + networks: + - services + labels: + - "traefik.enable=true" + - "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)" + - "traefik.http.routers.nextcloud.entrypoints=websecure" + - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" + - "traefik.http.routers.nextcloud.middlewares=authelia@docker" +``` + +### Internal Service (e.g., Portainer) + +```yaml +services: + portainer: + image: portainer/portainer-ce:latest + networks: + - services + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + labels: + - "traefik.enable=true" + - "traefik.http.routers.portainer.rule=Host(`docker.local.lemonlink.eu`)" + - "traefik.http.routers.portainer.entrypoints=internal" + - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" + - "traefik.http.routers.portainer.middlewares=authelia@docker" +``` + +## 🔍 Common Commands + +```bash +# View all logs +docker-compose logs -f + +# View specific service +docker-compose logs -f authelia + +# Restart service +docker-compose restart traefik + +# Check CrowdSec bans +docker-compose exec crowdsec cscli decisions list + +# Unban an IP +docker-compose exec crowdsec cscli decisions delete --ip 1.2.3.4 + +# Update everything +docker-compose pull && docker-compose up -d + +# Full reset (keeps data) +docker-compose down && docker-compose up -d + +# Complete wipe (⚠️ destroys data) +docker-compose down -v +``` + +## 🐛 Troubleshooting + +### "Bad Gateway" Error +- Check service is running: `docker-compose ps` +- Check service logs: `docker-compose logs [service]` +- Verify port in labels matches container port + +### Certificate Issues +- Check Cloudflare API credentials +- Verify DNS records +- Check Traefik logs for ACME errors +- Use staging first: change `certresolver` to `letsencrypt-staging` + +### Authelia Redirect Loop +- Check `session.domain` in authelia/configuration.yml +- Verify time sync: `timedatectl status` +- Clear browser cookies + +### Can't Access Internal Services +- Verify Tailscale is connected: `tailscale status` +- Check if port 8443 is bound to Tailscale IP +- Test locally: `curl -k https://localhost:8443` + +### CrowdSec Blocking Legitimate Traffic +```bash +# Check what's blocked +docker-compose exec crowdsec cscli decisions list + +# Remove false positive +docker-compose exec crowdsec cscli decisions delete --ip YOUR_IP + +# Add whitelist +docker-compose exec crowdsec cscli parsers install crowdsecurity/whitelists +``` + +## 📊 Monitoring Stack (Optional) + +```bash +# Start monitoring +docker-compose --profile monitoring up -d + +# Access (via Tailscale) +# - Grafana: https://grafana.local.lemonlink.eu:8443 +# - Prometheus: https://prometheus.local.lemonlink.eu:8443 +``` + +## 🔄 Backup Strategy + +```bash +# Backup script (run weekly) +#!/bin/bash +DATE=$(date +%Y%m%d) +tar czf backup-$DATE.tar.gz \ + traefik/ authelia/ crowdsec/ .env + +# Backup volumes +docker run --rm \ + -v lemonsec_authelia-data:/data \ + -v $(pwd):/backup \ + alpine tar czf /backup/authelia-$DATE.tar.gz -C /data . +``` + +## 🛡️ Security Checklist + +- [ ] Changed default Authelia password +- [ ] Enabled 2FA in Authelia +- [ ] Set up email notifications +- [ ] Configured CrowdSec notifications +- [ ] Enabled Cloudflare "Under Attack" mode for DDoS +- [ ] Set up regular backups +- [ ] Reviewed access logs weekly +- [ ] Updated images monthly + +## 📚 Next Steps + +1. **Add your services** - See `examples/` directory +2. **Configure monitoring** - Enable `--profile monitoring` +3. **Set up notifications** - Email/Discord alerts +4. **Review security** - Follow `docs/SECURITY.md` +5. **Customize** - Edit `docker-compose.override.yml` + +## 💬 Getting Help + +- **Traefik docs**: https://doc.traefik.io/traefik/ +- **Authelia docs**: https://www.authelia.com/ +- **CrowdSec docs**: https://docs.crowdsec.net/ +- **Logs**: Always check `docker-compose logs [service]` first diff --git a/README-DEPLOY.md b/README-DEPLOY.md new file mode 100644 index 0000000..3480171 --- /dev/null +++ b/README-DEPLOY.md @@ -0,0 +1,162 @@ +# 🚀 Deploy LemonSec Now + +This is the **fast path** to get LemonSec running on your Proxmox VM with Portainer. + +## Prerequisites + +- [ ] Proxmox VM with Docker and Portainer installed +- [ ] TrueNAS Scale VM with Nextcloud app installed +- [ ] Cloudflare account managing `lemonlink.eu` +- [ ] Ports 80/443 forwarded to Proxmox VM +- [ ] Git repository at `https://git.lemonlink.eu/impulsivefps/LemonSec` + +## Step 1: Prepare (2 minutes) + +### Get your info: +```bash +# Proxmox VM IP (where Portainer runs) +ip addr show | grep "inet " | head -2 +# e.g., 192.168.1.50 + +# TrueNAS IP +cat /etc/motd # or check TrueNAS UI +# e.g., 192.168.1.100 + +# Nextcloud Port (TrueNAS → Apps → Nextcloud) +# e.g., 9001 + +# Cloudflare API Key +# https://dash.cloudflare.com/profile/api-tokens → Global API Key +``` + +### Generate Secrets: +```bash +# Run this 3 times, save each output: +openssl rand -hex 32 +``` + +## Step 2: Push to Git (1 minute) + +```bash +cd LemonSec +git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git +git add . +git commit -m "Initial LemonSec deployment" +git push -u origin main +``` + +## Step 3: Deploy in Portainer (3 minutes) + +### 3.1 Create Stack +1. Open Portainer: `http://your-proxmox-vm:9000` +2. **Stacks** → **Add Stack** +3. Select **Repository** +4. Fill in: + - **Name**: `lemonsec` + - **Repository URL**: `https://git.lemonlink.eu/impulsivefps/LemonSec` + - **Compose path**: `docker-compose.yml` + +### 3.2 Add Environment Variables + +Copy/paste from `stack.env` and fill in your values: + +``` +CF_API_EMAIL=your-email@example.com +CF_API_KEY=your-cloudflare-global-api-key +TRUENAS_IP=192.168.1.100 +TRUENAS_NEXTCLOUD_PORT=9001 +AUTHELIA_JWT_SECRET=aaaa... (64 hex chars) +AUTHELIA_SESSION_SECRET=bbbb... (64 hex chars) +AUTHELIA_STORAGE_KEY=cccc... (64 hex chars) +TZ=Europe/Stockholm +``` + +### 3.3 Deploy +Click **Deploy the stack** + +Wait for containers to start (about 30 seconds). + +## Step 4: Configure CrowdSec (2 minutes) + +After deployment, get the API key: + +```bash +# SSH to Proxmox VM or use Portainer Console +docker exec crowdsec cscli bouncers add traefik-bouncer + +# Copy the key +``` + +Back in Portainer: +1. **Stacks** → **lemonsec** → **Editor** +2. Add environment variable: `CROWDSEC_API_KEY=the-key-you-copied` +3. Click **Update the stack** + +## Step 5: Configure TrueNAS (2 minutes) + +In TrueNAS Scale UI: +1. **Apps** → **Installed** → **Nextcloud** → **Edit** +2. Add Environment Variables: + ``` + NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu + OVERWRITEPROTOCOL=https + OVERWRITEHOST=cloud.lemonlink.eu + OVERWRITECLIURL=https://cloud.lemonlink.eu + TRUSTED_PROXIES=192.168.1.50 # Your Proxmox VM IP + ``` +3. **Save** + +## Step 6: Cloudflare DNS (1 minute) + +1. Login to [Cloudflare Dashboard](https://dash.cloudflare.com) +2. **DNS** → Add records: + +| Type | Name | Target | Proxy | +|------|------|--------|-------| +| A | cloud | YOUR_PUBLIC_IP | 🟠 Orange | +| A | auth | YOUR_PUBLIC_IP | 🟠 Orange | + +3. **SSL/TLS** → Set to **Full (strict)** + +## Step 7: Test (30 seconds) + +Open in browser: +``` +https://cloud.lemonlink.eu +``` + +You should see the Nextcloud login page! 🎉 + +## What You Got + +| Service | URL | Purpose | +|---------|-----|---------| +| Nextcloud | `https://cloud.lemonlink.eu` | Family file sharing | +| Authelia | `https://auth.lemonlink.eu` | SSO login portal | +| Traefik | `https://traefik.local.lemonlink.eu` | Reverse proxy dashboard | + +## Next Steps + +1. **Add family to Authelia**: Edit `authelia/users_database.yml` → push → "Pull and redeploy" +2. **Add more services**: Edit `docker-compose.yml` → add routers → push → redeploy +3. **Enable monitoring**: Uncomment monitoring profile in compose + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| "CF_API_EMAIL not set" | Check environment variables in Portainer | +| "502 Bad Gateway" | Verify TRUENAS_IP and PORT are correct | +| "Untrusted domain" | Check TrueNAS Nextcloud env vars | +| No SSL certificate | Check Cloudflare API key and DNS records | + +## Full Documentation + +- **[PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)** - Detailed Portainer guide +- **[SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md)** - TrueNAS specifics +- **[GIT-REPO-SETUP.md](GIT-REPO-SETUP.md)** - Git workflow +- **[MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md)** - If migrating from NPM + +--- + +**You're done!** Family can now access Nextcloud at `https://cloud.lemonlink.eu` diff --git a/README.md b/README.md new file mode 100644 index 0000000..66b1a8e --- /dev/null +++ b/README.md @@ -0,0 +1,312 @@ +# LemonSec 🍋 + +A comprehensive, security-focused reverse proxy and authentication stack for homelab and small business deployments. + +## Architecture + +``` +Internet → Cloudflare → Traefik → {Authelia → Your Services} + ↓ + CrowdSec (threat detection) + ↓ + AdGuard Home (internal DNS) +``` + +### For TrueNAS Scale Users +If you run Nextcloud on TrueNAS Scale (separate VM), LemonSec acts as the secure gateway: + +``` +Internet → Cloudflare → Traefik (Proxmox VM) → TrueNAS Nextcloud + ↓ + Authelia (family SSO) +``` + +See [SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md) for step-by-step guide. + +## Components + +| Component | Purpose | External | Internal | +|-----------|---------|----------|----------| +| **Traefik** | Reverse Proxy & Load Balancer | ✅ 443/80 | ✅ 8443 | +| **Authelia** | SSO & 2FA Authentication | ✅ auth.lemonlink.eu | ✅ | +| **CrowdSec** | Intrusion Detection/Prevention | ✅ | ✅ | +| **AdGuard Home** | DNS + Ad Blocking | ❌ | ✅ DNS | +| **Redis** | Session Storage | ❌ | ✅ | + +## Network Segmentation + +### External Network (`traefik-external`) +- **Entry**: Cloudflare (orange cloud) +- **Security**: CrowdSec + Authelia 2FA +- **Examples**: nextcloud.lemonlink.eu, vault.lemonlink.eu + +### Internal Network (`traefik-internal`) +- **Entry**: Tailscale/VPN only +- **Security**: Authelia (one_factor on trusted networks) +- **Examples**: traefik.local.lemonlink.eu, portainer.local.lemonlink.eu + +## Quick Start + +### 🚀 Deploy via Portainer (Recommended for Proxmox) + +The easiest way to deploy on Proxmox with Portainer: + +1. **Push to Git**: +```bash +git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git +git push -u origin main +``` + +2. **In Portainer**: +- **Stacks** → **Add Stack** → **Repository** +- URL: `https://git.lemonlink.eu/impulsivefps/LemonSec` +- Add environment variables from `stack.env` +- **Deploy** + +**Full Portainer guide**: [PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md) + +### 🚀 TrueNAS + Nextcloud Quick Start + +If your main goal is securing TrueNAS Nextcloud for family: + +```bash +# 1. Clone and configure +cd LemonSec +cp .env.example .env +# Edit: Add Cloudflare API, TrueNAS_IP, Nextcloud port + +# 2. Setup +./setup.ps1 # or create secrets manually + +# 3. Deploy +docker-compose up -d +docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d + +# 4. Configure TrueNAS Nextcloud +# Apps → Nextcloud → Add environment variables +# NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu +# OVERWRITEPROTOCOL=https +# TRUSTED_PROXIES=YOUR_PROXMOX_VM_IP +``` + +**Full guide**: [SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md) + +### Migrating from Nginx Proxy Manager? + +See [MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md) for side-by-side migration guide. + +--- + +### 1. Prerequisites + +- Docker & Docker Compose installed +- Domain managed by Cloudflare +- Tailscale installed on server +- Ports 80/443 forwarded to Proxmox server + +### 2. Configuration + +```bash +# Clone and setup +cd LemonSec + +# Copy and edit environment +cp .env.example .env +nano .env + +# Run setup script (generates secrets) +./setup.ps1 # On Windows +# or +bash setup.sh # On Linux +``` + +### 3. Cloudflare Setup + +See [CLOUDFLARE.md](docs/CLOUDFLARE.md) for detailed DNS configuration. + +Quick checklist: +- [ ] Create API token with DNS:Edit permissions +- [ ] Add A record pointing to your server IP +- [ ] Enable orange cloud (proxy) +- [ ] Set SSL/TLS mode to "Full (strict)" +- [ ] Enable "Always Use HTTPS" + +### 4. Deploy + +```bash +# Start core services +docker-compose up -d + +# Check Traefik is working +docker-compose logs -f traefik + +# Generate CrowdSec API key +docker-compose exec crowdsec cscli bouncers add traefik-bouncer +# Add key to .env, then restart +docker-compose up -d + +# Setup Authelia users +docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD' +# Add hash to authelia/users_database.yml +``` + +### 5. Add Your Services + +See [examples/](examples/) for service templates. + +```yaml +# docker-compose.override.yml or separate file +services: + myapp: + image: myapp:latest + networks: + - services + labels: + - "traefik.enable=true" + - "traefik.http.routers.myapp.rule=Host(`myapp.lemonlink.eu`)" + - "traefik.http.routers.myapp.entrypoints=websecure" + - "traefik.http.routers.myapp.tls.certresolver=letsencrypt" + - "traefik.http.routers.myapp.middlewares=authelia@docker" +``` + +## Security Features + +### 1. Multi-Factor Authentication +- TOTP (Google Authenticator, Authy, etc.) +- WebAuthn/FIDO2 (YubiKey, Windows Hello, etc.) +- Duo Push (with configuration) + +### 2. Threat Detection (CrowdSec) +- Real-time log analysis +- Community blocklists +- Automatic IP banning +- Metrics and alerts + +### 3. Network Isolation +- Internal services inaccessible from internet +- External services protected by auth +- Docker network segmentation + +### 4. TLS/SSL +- Automatic Let's Encrypt certificates +- Wildcard certificates for subdomains +- Modern TLS 1.3 configuration + +### 5. Security Headers +- HSTS (1 year) +- CSP (Content Security Policy) +- X-Frame-Options, X-Content-Type-Options +- Referrer-Policy + +## Directory Structure + +``` +LemonSec/ +├── docker-compose.yml # Main stack +├── .env # Your secrets (gitignored) +├── .env.example # Template +├── setup.ps1 # Windows setup +├── traefik/ +│ ├── traefik.yml # Static config +│ ├── dynamic/ # Dynamic config +│ │ ├── middlewares.yml # Security middlewares +│ │ └── tls.yml # TLS options +│ ├── crowdsec/ # CrowdSec bouncer config +│ └── logs/ # Access logs +├── authelia/ +│ ├── configuration.yml # Authelia settings +│ └── users_database.yml # User accounts +├── crowdsec/ +│ └── acquis.yaml # Log sources +├── adguard/ # AdGuard data (volumes) +├── monitoring/ # Prometheus/Grafana configs +├── examples/ # Service examples +└── docs/ # Documentation +``` + +## Common Operations + +### View Logs +```bash +# All services +docker-compose logs -f + +# Specific service +docker-compose logs -f traefik + +# CrowdSec decisions +docker-compose exec crowdsec cscli decisions list + +# CrowdSec metrics +docker-compose exec crowdsec cscli metrics +``` + +### Update +```bash +# Pull latest images +docker-compose pull + +# Recreate containers +docker-compose up -d +``` + +### Backup +```bash +# Backup volumes +docker run --rm -v lemonsec_authelia-data:/data -v $(pwd):/backup alpine tar czf /backup/authelia-backup.tar.gz -C /data . +docker run --rm -v lemonsec_crowdsec-data:/data -v $(pwd):/backup alpine tar czf /backup/crowdsec-backup.tar.gz -C /data . + +# Backup configs +tar czf lemonsec-config-backup.tar.gz traefik/ authelia/ crowdsec/ .env +``` + +### Add User to Authelia +```bash +# Generate password hash +docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'NEWPASSWORD' + +# Edit authelia/users_database.yml +# Add user entry with hash +# Restart authelia +docker-compose restart authelia +``` + +## Troubleshooting + +### Traefik not getting certificates +1. Check Cloudflare API credentials in `.env` +2. Verify DNS records point to your IP +3. Check Traefik logs: `docker-compose logs traefik` +4. Test with staging first: change `certresolver` to `letsencrypt-staging` + +### Authelia redirect loops +1. Check `session.domain` matches your domain +2. Verify cookies aren't blocked +3. Check time sync on server: `ntpdate -s time.cloudflare.com` + +### CrowdSec blocking legitimate traffic +```bash +# Check decisions +docker-compose exec crowdsec cscli decisions list + +# Remove false positive +docker-compose exec crowdsec cscli decisions delete --ip YOUR_IP + +# Add whitelist +docker-compose exec crowdsec cscli parsers install crowdsecurity/whitelists +``` + +## Security Checklist + +- [ ] Change all default passwords +- [ ] Enable 2FA for all users +- [ ] Set up email notifications for Authelia +- [ ] Configure CrowdSec notifications +- [ ] Review Cloudflare security settings +- [ ] Enable fail2ban on host (optional) +- [ ] Regular backups +- [ ] Update images monthly + +## License + +MIT - Use at your own risk. This is a homelab security stack, not enterprise-grade. diff --git a/SETUP-TRUENAS-NEXTCLOUD.md b/SETUP-TRUENAS-NEXTCLOUD.md new file mode 100644 index 0000000..382702d --- /dev/null +++ b/SETUP-TRUENAS-NEXTCLOUD.md @@ -0,0 +1,244 @@ +# Quick Setup: TrueNAS Nextcloud + LemonSec + +## Your Setup +- ✅ Proxmox VM running Docker/Portainer +- ✅ TrueNAS Scale VM running Nextcloud +- ✅ Need: Family access to Nextcloud via secure domain + +## Timeline: 15 minutes to working Nextcloud + +--- + +## Phase 1: Prepare (5 min) + +### 1. Get Your IPs +```bash +# On Proxmox VM (where LemonSec will run) +ip addr show | grep "inet " | head -3 +# Note: e.g., 192.168.1.50 + +# On TrueNAS Scale VM +# Check TrueNAS UI or: ip addr +# Note: e.g., 192.168.1.100 + +# Get Nextcloud port in TrueNAS +# Apps → Nextcloud → Note the Node Port (e.g., 9001) +``` + +### 2. Configure Environment +```bash +cd LemonSec +cp .env.example .env +nano .env +``` + +Fill in: +``` +CF_API_EMAIL=youremail@example.com +CF_API_KEY=your-cloudflare-global-api-key +TZ=Europe/Stockholm +TRUENAS_IP=192.168.1.100 +TRUENAS_NEXTCLOUD_PORT=9001 +``` + +### 3. Generate Secrets +```bash +# PowerShell (on Windows) +$jwt = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) +$jwt | Set-Content secrets/authelia_jwt_secret.txt -NoNewline + +$session = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) +$session | Set-Content secrets/authelia_session_secret.txt -NoNewline + +$storage = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) }) +$storage | Set-Content secrets/authelia_storage_key.txt -NoNewline + +# Or on Linux: +# openssl rand -hex 32 > secrets/authelia_jwt_secret.txt +# openssl rand -hex 32 > secrets/authelia_session_secret.txt +# openssl rand -hex 32 > secrets/authelia_storage_key.txt +``` + +--- + +## Phase 2: Deploy Core (5 min) + +### 1. Start LemonSec +```bash +docker-compose up -d + +# Check logs +docker-compose logs -f traefik +# Wait for: "Configuration loaded from files..." +# Press Ctrl+C when stable +``` + +### 2. Setup CrowdSec +```bash +docker-compose exec crowdsec cscli bouncers add traefik-bouncer +# Copy the API key + +# Edit .env, add: +# CROWDSEC_API_KEY=paste-key-here + +# Restart +docker-compose up -d +``` + +### 3. Start External Routing +```bash +docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d +``` + +--- + +## Phase 3: Cloudflare DNS (3 min) + +Login to [Cloudflare Dashboard](https://dash.cloudflare.com) + +### Add DNS Records +| Type | Name | Target | Proxy | +|------|------|--------|-------| +| A | cloud | YOUR_PROXMOX_PUBLIC_IP | 🟠 | +| A | auth | YOUR_PROXMOX_PUBLIC_IP | 🟠 | +| A | * | YOUR_PROXMOX_PUBLIC_IP | 🟠 | + +### SSL/TLS Settings +- **SSL/TLS encryption**: Full (strict) +- **Always Use HTTPS**: ON + +--- + +## Phase 4: TrueNAS Nextcloud Config (2 min) + +### In TrueNAS Scale: + +1. **Apps** → **Installed Applications** → **Nextcloud** → **Edit** +2. **Add Environment Variables**: + ``` + NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu + OVERWRITEPROTOCOL=https + OVERWRITEHOST=cloud.lemonlink.eu + OVERWRITECLIURL=https://cloud.lemonlink.eu + TRUSTED_PROXIES=192.168.1.50 + ``` + (Replace 192.168.1.50 with your Proxmox VM IP) + +3. **Save** and wait for app to update + +--- + +## Phase 5: Test (Immediately) + +### 1. Test Nextcloud +```bash +# From anywhere +curl -I https://cloud.lemonlink.eu +# Should return 200 or redirect to login +``` + +### 2. Access Web UI +Open: `https://cloud.lemonlink.eu` + +You should see Nextcloud login page! + +### 3. Create Family Accounts +Login as admin → **Users** → **Create** for each family member + +--- + +## Optional: Add Authelia Protection + +If you want extra login security before Nextcloud: + +### Edit docker-compose.external.yml +```yaml +# Change this line: +- "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file" +# From: (no authelia) +# - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file" +``` + +### Restart +```bash +docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d +``` + +### Setup Authelia User +```bash +# Generate password hash +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 \ + --password 'FamilyPassword123!' + +# Edit authelia/users_database.yml +# Add family members with the hash + +# Restart authelia +docker-compose restart authelia +``` + +Now family logs in to Authelia first, then Nextcloud. + +--- + +## For Family Members + +Send them this info: + +``` +🍋 Your Nextcloud Access + +URL: https://cloud.lemonlink.eu + +Login with your credentials (created by admin) + +Mobile Apps: +- iOS: App Store → "Nextcloud" +- Android: Play Store → "Nextcloud" +- Desktop: nextcloud.com/install + +Server address in apps: https://cloud.lemonlink.eu +``` + +--- + +## Troubleshooting + +### "Access through untrusted domain" +```bash +# Shell into TrueNAS +k3s kubectl exec -it -n ix-nextcloud deployment/ix-nextcloud -- /bin/sh + +# Check config +cat /var/www/html/config/config.php | grep trusted + +# Should include 'cloud.lemonlink.eu' +``` + +### "502 Bad Gateway" +- Check TrueNAS IP and port in .env +- Verify Nextcloud app is running in TrueNAS +- Test direct access: `curl http://TRUENAS_IP:PORT` + +### "Too Many Redirects" +- Ensure OVERWRITEPROTOCOL=https is set +- Check Cloudflare SSL mode is "Full (strict)" + +--- + +## Next Steps (After Nextcloud Works) + +1. ✅ **Backup** - Set up automatic backups +2. ✅ **Monitoring** - Enable `--profile monitoring` +3. ✅ **More Services** - Add Portainer, etc. +4. ✅ **Security** - Review `docs/SECURITY.md` + +--- + +## Files You Modified + +Keep backups of these: +- `.env` - Your secrets and IPs +- `authelia/users_database.yml` - Family logins +- `docker-compose.external.yml` - Service routing diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..87763a6 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,215 @@ +# LemonSec Setup Guide + +Complete setup instructions for different deployment methods. + +## Table of Contents + +1. [Portainer Git Deployment](#portainer-git-deployment-recommended) (Recommended) +2. [Docker Compose Manual](#docker-compose-manual) +3. [TrueNAS Nextcloud Specifics](#truenas-nextcloud-setup) +4. [Migration from NPM](#migration-from-nginx-proxy-manager) + +--- + +## Portainer Git Deployment (Recommended) + +Best for Proxmox users with Portainer already installed. + +### Prerequisites + +- Portainer running on Proxmox VM +- Git repository at `https://git.lemonlink.eu/impulsivefps/LemonSec` +- Cloudflare account +- TrueNAS Scale with Nextcloud + +### Quick Steps + +1. **Push code to Git**: +```bash +cd LemonSec +git init +git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git +git add . +git commit -m "Initial commit" +git push -u origin main +``` + +2. **Generate Secrets**: +```bash +openssl rand -hex 32 # Run 3 times +``` +Save the 3 outputs for AUTHELIA_*_SECRET variables. + +3. **Deploy in Portainer**: +- Stacks → Add Stack → Repository +- URL: `https://git.lemonlink.eu/impulsivefps/LemonSec` +- Add environment variables (see below) +- Deploy + +### Required Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `CF_API_EMAIL` | Cloudflare email | `you@example.com` | +| `CF_API_KEY` | Cloudflare API key | `global-api-key` | +| `TRUENAS_IP` | TrueNAS VM IP | `192.168.1.100` | +| `TRUENAS_NEXTCLOUD_PORT` | Nextcloud port | `9001` | +| `AUTHELIA_JWT_SECRET` | Random hex string | `openssl rand -hex 32` | +| `AUTHELIA_SESSION_SECRET` | Random hex string | `openssl rand -hex 32` | +| `AUTHELIA_STORAGE_KEY` | Random hex string | `openssl rand -hex 32` | + +**Full detailed guide**: [PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md) + +--- + +## Docker Compose Manual + +For systems without Portainer or for development. + +### Prerequisites + +- Docker 20.10+ +- Docker Compose 2.0+ +- Git + +### Steps + +1. **Clone repository**: +```bash +git clone https://git.lemonlink.eu/impulsivefps/LemonSec.git +cd LemonSec +``` + +2. **Configure environment**: +```bash +cp .env.example .env +nano .env +``` + +3. **Generate secrets**: +```bash +mkdir -p secrets +openssl rand -hex 32 > secrets/authelia_jwt_secret.txt +openssl rand -hex 32 > secrets/authelia_session_secret.txt +openssl rand -hex 32 > secrets/authelia_storage_key.txt +``` + +4. **Deploy**: +```bash +docker-compose up -d +``` + +5. **Setup CrowdSec**: +```bash +docker-compose exec crowdsec cscli bouncers add traefik-bouncer +# Add key to .env and restart +``` + +--- + +## TrueNAS Nextcloud Setup + +If your Nextcloud runs on TrueNAS Scale (separate VM). + +### Architecture + +``` +Internet → Cloudflare → Traefik (Proxmox) → TrueNAS (Nextcloud) +``` + +### Configuration + +1. **Get TrueNAS Info**: + - TrueNAS IP: `192.168.1.100` + - Nextcloud Port: Check Apps → Nextcloud (e.g., `9001`) + +2. **Set environment variables**: +``` +TRUENAS_IP=192.168.1.100 +TRUENAS_NEXTCLOUD_PORT=9001 +``` + +3. **Configure TrueNAS Nextcloud**: +In TrueNAS → Apps → Nextcloud → Edit, add: +``` +NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu +OVERWRITEPROTOCOL=https +OVERWRITEHOST=cloud.lemonlink.eu +TRUSTED_PROXIES=192.168.1.50 # Your Proxmox VM IP +``` + +4. **DNS**: +Cloudflare → DNS: +- A record: `cloud` → YOUR_PUBLIC_IP (orange cloud) + +5. **Test**: +Visit `https://cloud.lemonlink.eu` + +**Detailed guide**: [SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md) + +--- + +## Migration from Nginx Proxy Manager + +If you're currently using NPM and want to migrate to LemonSec. + +### Strategy + +1. **Parallel running** (recommended): + - Deploy LemonSec alongside NPM + - Migrate services one by one + - Test thoroughly + - Only then remove NPM + +2. **Direct migration**: + - Export NPM config + - Convert to Traefik format + - Deploy and verify + +### Migration Mapping + +| NPM | Traefik | +|-----|---------| +| Proxy Hosts | Labels on router containers | +| SSL Certificates | Automatic via certresolver | +| Access Lists | Authelia middleware | +| Custom Locations | Path rules | +| Redirection Hosts | Redirect middleware | + +**Full migration guide**: [MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md) + +--- + +## Post-Deployment Checklist + +- [ ] Nextcloud accessible at `https://cloud.lemonlink.eu` +- [ ] SSL certificate valid (green lock) +- [ ] Family can login with Nextcloud credentials +- [ ] Mobile apps sync successfully +- [ ] CrowdSec showing decisions (`docker exec crowdsec cscli decisions list`) +- [ ] Authelia accessible at `https://auth.lemonlink.eu` +- [ ] Backups configured + +## Troubleshooting + +### "CF_API_EMAIL not set" +→ Check environment variables in Portainer UI + +### "502 Bad Gateway" for Nextcloud +→ Verify TRUENAS_IP and TRUENAS_NEXTCLOUD_PORT +→ Test: `curl http://TRUENAS_IP:PORT` from Proxmox VM + +### "Access through untrusted domain" +→ Add domain to TrueNAS Nextcloud environment variables + +### SSL Certificate issues +→ Check Cloudflare API credentials +→ Verify DNS records exist in Cloudflare + +## Next Steps + +1. **Add family to Authelia** (if using SSO) +2. **Enable monitoring** (Prometheus/Grafana) +3. **Add more services** (Vaultwarden, etc.) +4. **Configure backups** +5. **Review security settings** diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..a1a083c --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,110 @@ +# LemonSec - Quick Reference + +## 🎯 Your Setup +- **Proxmox VM**: Docker/Portainer + LemonSec stack +- **TrueNAS Scale VM**: Nextcloud app +- **Goal**: Secure family access to Nextcloud via `cloud.lemonlink.eu` +- **Deploy Method**: Portainer Git Repository + +## 🚀 Deploy via Portainer (5 min) + +### 1. Push to Git +```bash +cd LemonSec +git remote add origin https://git.lemonlink.eu/impulsivefps/LemonSec.git +git add . +git commit -m "Initial deployment" +git push -u origin main +``` + +### 2. Portainer UI +- **Stacks** → **Add Stack** → **Repository** +- **URL**: `https://git.lemonlink.eu/impulsivefps/LemonSec` +- **Compose Path**: `docker-compose.yml` + +### 3. Environment Variables +Copy from `stack.env` and fill in: + +| Variable | Value | +|----------|-------| +| `CF_API_EMAIL` | your@email.com | +| `CF_API_KEY` | Cloudflare API key | +| `TRUENAS_IP` | 192.168.1.100 | +| `TRUENAS_NEXTCLOUD_PORT` | 9001 | +| `AUTHELIA_JWT_SECRET` | `openssl rand -hex 32` | +| `AUTHELIA_SESSION_SECRET` | `openssl rand -hex 32` | +| `AUTHELIA_STORAGE_KEY` | `openssl rand -hex 32` | + +### 4. Deploy +Click **Deploy the stack** + +### 5. Setup CrowdSec +```bash +docker exec crowdsec cscli bouncers add traefik-bouncer +# Copy key, add to Portainer env vars, redeploy +``` + +### 6. TrueNAS Config +TrueNAS → Apps → Nextcloud → Edit, add env: +``` +NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu +OVERWRITEPROTOCOL=https +TRUSTED_PROXIES=192.168.1.50 # Proxmox VM IP +``` + +### 7. Cloudflare DNS +- A record: `cloud` → YOUR_PUBLIC_IP (orange cloud) + +### Done! +Visit: `https://cloud.lemonlink.eu` ✅ + +**Full guide**: [PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md) + +--- + +## 📁 Repository Structure + +| Path | Purpose | +|------|---------| +| `docker-compose.yml` | Main stack - Traefik, Authelia, CrowdSec, Nextcloud router | +| `stack.env` | Environment variable template for Portainer | +| `traefik/` | Traefik configuration files | +| `authelia/` | Authelia config and user database | +| `crowdsec/` | CrowdSec acquisition config | + +## 🔧 Customization + +### Add Family to Authelia +Edit `authelia/users_database.yml` → push → Portainer "Pull and redeploy" + +### Add More Services +Edit `docker-compose.yml` → add router container → push → redeploy + +### Update Stack +1. Edit files locally +2. `git commit -am "Update" && git push` +3. Portainer → Stacks → lemonsec → "Pull and redeploy" + +## 📚 Documentation + +- **[PORTAINER-DEPLOY.md](PORTAINER-DEPLOY.md)** - Detailed Portainer deployment +- **[SETUP-TRUENAS-NEXTCLOUD.md](SETUP-TRUENAS-NEXTCLOUD.md)** - TrueNAS specific setup +- **[MIGRATE-FROM-NPM.md](MIGRATE-FROM-NPM.md)** - NPM migration guide +- **[docs/CLOUDFLARE.md](docs/CLOUDFLARE.md)** - DNS/SSL configuration + +## 🆘 Troubleshooting + +| Issue | Solution | +|-------|----------| +| "CF_API_EMAIL not set" | Check env vars in Portainer UI | +| "502 Bad Gateway" | Verify TRUENAS_IP and PORT | +| "Untrusted domain" | Add domain to TrueNAS Nextcloud env | +| SSL errors | Check Cloudflare API credentials | + +## ✅ Success Checklist + +- [ ] `https://cloud.lemonlink.eu` loads Nextcloud +- [ ] Family can login with Nextcloud accounts +- [ ] Mobile apps work +- [ ] SSL certificate valid +- [ ] CrowdSec shows decisions diff --git a/authelia/configuration.yml b/authelia/configuration.yml new file mode 100644 index 0000000..699c419 --- /dev/null +++ b/authelia/configuration.yml @@ -0,0 +1,203 @@ +# Authelia Configuration +# Full reference: https://www.authelia.com/configuration/ + +server: + address: tcp://0.0.0.0:9091 + endpoints: + enable_pprof: false + enable_expvars: false + disable_healthcheck: false + tls: + key: '' + certificate: '' + +log: + level: info + format: text + file_path: /config/authelia.log + keep_stdout: true + +telemetry: + metrics: + enabled: false + +totp: + issuer: lemonlink.eu + algorithm: sha1 + digits: 6 + period: 30 + skew: 1 + secret_size: 32 + +webauthn: + disable: false + display_name: LemonLink + attestation_conveyance_preference: indirect + user_verification: preferred + timeout: 60s + +ntp: + address: "time.cloudflare.com:123" + version: 4 + max_desync: 3s + disable_startup_check: false + disable_failure: false + +authentication_backend: + password_reset: + disable: false + custom_url: '' + + # File-based user database (for small deployments) + # For LDAP/Active Directory, see: https://www.authelia.com/configuration/authentication/ldap/ + file: + path: /users_database.yml + watch: true + search: + email: true + case_insensitive: false + password: + algorithm: argon2id + argon2id: + variant: argon2id + iterations: 3 + memory: 65536 + parallelism: 4 + key_length: 32 + salt_length: 16 + +password_policy: + standard: + enabled: false + min_length: 8 + max_length: 0 + require_uppercase: true + require_lowercase: true + require_number: true + require_special: true + zxcvbn: + enabled: true + min_score: 3 + +access_control: + default_policy: two_factor + networks: + - name: internal + networks: + - 100.64.0.0/10 # Tailscale + - 10.0.0.0/8 # Private + - 172.16.0.0/12 # Private + - 192.168.0.0/16 # Private + - fc00::/7 # IPv6 ULA + + rules: + # Public endpoints (no auth) + - domain: + - "auth.lemonlink.eu" + policy: bypass + + # External services - strict 2FA + - domain: + - "*.lemonlink.eu" + policy: two_factor + + # Internal services - can use one_factor on trusted networks + - domain: + - "*.local.lemonlink.eu" + policy: one_factor + networks: + - internal + + # Internal services from external - require 2FA + - domain: + - "*.local.lemonlink.eu" + policy: two_factor + +session: + name: authelia_session + domain: lemonlink.eu + same_site: lax + secret: ${AUTHELIA_SESSION_SECRET} # Set via environment variable + expiration: 1h + inactivity: 5m + remember_me_duration: 1M + + redis: + host: redis + port: 6379 + database_index: 0 + maximum_active_connections: 8 + minimum_idle_connections: 0 + tls: + enabled: false + +regulation: + max_retries: 3 + find_time: 2m + ban_time: 5m + + # CrowdSec integration + # crowdsec: + # enabled: true + # host: crowdsec + # port: 8080 + # key: ${CROWDSEC_API_KEY} + +storage: + encryption_key: ${AUTHELIA_STORAGE_KEY} # Set via environment variable + local: + path: /config/db.sqlite3 + +notifier: + disable_startup_check: false + + # SMTP configuration for email notifications + # smtp: + # address: smtp.gmail.com:587 + # timeout: 5s + # username: your-email@gmail.com + # password: your-app-password + # sender: "Authelia " + # identifier: lemonlink.eu + # subject: "[Authelia] {title}" + # startup_check_address: test@authelia.com + # disable_require_tls: false + # disable_html_emails: false + # tls: + # skip_verify: false + # minimum_version: TLS1.2 + +identity_providers: + oidc: + cors: + endpoints: + - authorization + - token + - revocation + - introspection + allowed_origins: [] + allowed_origins_from_client_redirect_uris: false + + # Clients configuration + clients: + # Grafana OAuth + - client_id: grafana + client_name: Grafana + client_secret: ${GRAFANA_OAUTH_SECRET} + public: false + authorization_policy: two_factor + require_pkce: true + pkce_challenge_method: S256 + redirect_uris: + - https://grafana.local.lemonlink.eu/login/generic_oauth + scopes: + - openid + - profile + - groups + - email + userinfo_signed_response_alg: none + + # Add more clients as needed + # - client_id: nextcloud + # client_name: Nextcloud + # ... diff --git a/authelia/users_database.yml b/authelia/users_database.yml new file mode 100644 index 0000000..0bdb6ff --- /dev/null +++ b/authelia/users_database.yml @@ -0,0 +1,19 @@ +# Authelia Users Database +# Generate passwords with: docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD' + +users: + admin: + displayname: "Administrator" + password: "$argon2id$v=19$m=65536,t=3,p=4$VEVNUExBQ0VIRUxMT1dPVUJE$EXAMPLEHASHCHANGE" # Change this! + email: admin@lemonlink.eu + groups: + - admins + - users + + # Add more users here + # username: + # displayname: "Full Name" + # password: "hashed_password" + # email: user@example.com + # groups: + # - users diff --git a/crowdsec/acquis.yaml b/crowdsec/acquis.yaml new file mode 100644 index 0000000..aef85b9 --- /dev/null +++ b/crowdsec/acquis.yaml @@ -0,0 +1,24 @@ +# CrowdSec acquisition configuration +# This tells CrowdSec where to find logs to analyze + +filenames: + # Traefik access logs + - /var/log/traefik/access.log + # Traefik application logs + - /var/log/traefik/traefik.log +labels: + type: traefik + +--- +# System authentication logs (if available) +filenames: + - /var/log/auth.log +labels: + type: syslog + +--- +# Journald (if running on host) +journald_filter: + - "SYSLOG_IDENTIFIER=sshd" +labels: + type: syslog diff --git a/docker-compose.external.yml b/docker-compose.external.yml new file mode 100644 index 0000000..d3de652 --- /dev/null +++ b/docker-compose.external.yml @@ -0,0 +1,85 @@ +# External Services Router +# For services running on other VMs (TrueNAS, etc.) + +version: "3.8" + +networks: + services: + external: true # Connect to main LemonSec network + +services: + # ============================================================================ + # EXTERNAL NEXTCLOUD (on TrueNAS Scale) + # ============================================================================ + # This is just a "dummy" container that tells Traefik how to route to TrueNAS + # No actual container runs - it's just configuration + + nextcloud-router: + image: alpine:latest + container_name: nextcloud-router + restart: "no" + command: "echo 'This is a routing configuration container'" + networks: + - services + labels: + - "traefik.enable=true" + + # EXTERNAL ACCESS - Public URL for family + - "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)" + - "traefik.http.routers.nextcloud.entrypoints=websecure" + - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" + + # Service points to TrueNAS Nextcloud + # Replace TRUENAS_IP with your TrueNAS VM IP + - "traefik.http.services.nextcloud.loadbalancer.server.url=http://${TRUENAS_IP}:PORT" + # If TrueNAS Nextcloud uses HTTPS internally: + # - "traefik.http.services.nextcloud.loadbalancer.server.url=https://${TRUENAS_IP}:PORT" + # - "traefik.http.services.nextcloud.loadbalancer.server.scheme=https" + # - "traefik.http.services.nextcloud.loadbalancer.serversTransport=insecureTransport@file" + + # AUTHENTICATION OPTIONS (choose one): + + # Option A: No Authelia (Nextcloud handles auth itself) + # Good for: Family already has Nextcloud accounts + # - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file" + + # Option B: Authelia one_factor (password only, no 2FA) + # Good for: Family-friendly, still protected from internet + - "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file" + + # Option C: Authelia two_factor (password + 2FA) + # Good for: Maximum security + # - "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file" + + # WebDAV/CalDAV support (important for Nextcloud apps) + - "traefik.http.routers.nextcloud-dav.rule=Host(`cloud.lemonlink.eu`) && PathPrefix(`/.well-known/carddav`,`/.well-known/caldav`,`/remote.php`)" + - "traefik.http.routers.nextcloud-dav.entrypoints=websecure" + - "traefik.http.routers.nextcloud-dav.tls.certresolver=letsencrypt" + - "traefik.http.routers.nextcloud-dav.service=nextcloud" + - "traefik.http.routers.nextcloud-dav.middlewares=authelia@docker" + + # Upload size (important for Nextcloud) + - "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https" + - "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Frame-Options=SAMEORIGIN" + - "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file,nextcloud-headers" + + # ============================================================================ + # OTHER EXTERNAL SERVICES + # ============================================================================ + + # TrueNAS Web UI (internal only) + truenas-router: + image: alpine:latest + container_name: truenas-router + restart: "no" + command: "echo 'TrueNAS routing'" + networks: + - services + labels: + - "traefik.enable=true" + - "traefik.http.routers.truenas.rule=Host(`nas.local.lemonlink.eu`)" + - "traefik.http.routers.truenas.entrypoints=internal" + - "traefik.http.routers.truenas.tls.certresolver=letsencrypt" + - "traefik.http.routers.truenas.middlewares=authelia@docker" + - "traefik.http.services.truenas.loadbalancer.server.url=https://${TRUENAS_IP}:443" + - "traefik.http.services.truenas.loadbalancer.serversTransport=insecureTransport@file" diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example new file mode 100644 index 0000000..5cecf92 --- /dev/null +++ b/docker-compose.override.yml.example @@ -0,0 +1,110 @@ +# Docker Compose Override +# Copy this file to docker-compose.override.yml and customize +# This file is automatically loaded by docker-compose + +version: "3.8" + +services: + # ============================================================================ + # Traefik Customization + # ============================================================================ + traefik: + # Bind internal entrypoint to Tailscale IP only + ports: + - "80:80" + - "443:443" + # Uncomment and set TAILSCALE_IP in .env + # - "${TAILSCALE_IP}:8443:8443" + + # Additional volumes for custom certs + # volumes: + # - ./custom-certs:/certs:ro + + # ============================================================================ + # Add Your Services Below + # ============================================================================ + + # Example: Static website + # website: + # image: nginx:alpine + # container_name: website + # restart: unless-stopped + # networks: + # - traefik-external + # volumes: + # - ./website:/usr/share/nginx/html:ro + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.website.rule=Host(`lemonlink.eu`) || Host(`www.lemonlink.eu`)" + # - "traefik.http.routers.website.entrypoints=websecure" + # - "traefik.http.routers.website.tls.certresolver=letsencrypt" + # # No Authelia for public website + # - "traefik.http.services.website.loadbalancer.server.port=80" + + # Example: Bookmarks service + # linkding: + # image: sissbruecker/linkding:latest + # container_name: linkding + # restart: unless-stopped + # networks: + # - services + # environment: + # - LD_SUPERUSER_NAME=admin + # - LD_SUPERUSER_PASSWORD=${LINKDING_ADMIN_PASSWORD} + # volumes: + # - linkding-data:/etc/linkding/data + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.linkding.rule=Host(`bookmarks.lemonlink.eu`)" + # - "traefik.http.routers.linkding.entrypoints=websecure" + # - "traefik.http.routers.linkding.tls.certresolver=letsencrypt" + # - "traefik.http.routers.linkding.middlewares=authelia@docker" + + # Example: File browser (internal only) + # filebrowser: + # image: filebrowser/filebrowser:latest + # container_name: filebrowser + # restart: unless-stopped + # networks: + # - services + # volumes: + # - /path/to/your/files:/srv + # - filebrowser-data:/database + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.filebrowser.rule=Host(`files.local.lemonlink.eu`)" + # - "traefik.http.routers.filebrowser.entrypoints=internal" + # - "traefik.http.routers.filebrowser.tls.certresolver=letsencrypt" + # - "traefik.http.routers.filebrowser.middlewares=authelia@docker" + + # Example: Media server (Jellyfin) + # jellyfin: + # image: jellyfin/jellyfin:latest + # container_name: jellyfin + # restart: unless-stopped + # networks: + # - services + # environment: + # - PUID=1000 + # - PGID=1000 + # volumes: + # - jellyfin-config:/config + # - /path/to/media:/media:ro + # labels: + # - "traefik.enable=true" + # # External access with auth + # - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.lemonlink.eu`)" + # - "traefik.http.routers.jellyfin.entrypoints=websecure" + # - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" + # - "traefik.http.routers.jellyfin.middlewares=authelia@docker" + # # Internal access (direct) + # - "traefik.http.routers.jellyfin-internal.rule=Host(`jellyfin.local.lemonlink.eu`)" + # - "traefik.http.routers.jellyfin-internal.entrypoints=internal" + # - "traefik.http.routers.jellyfin-internal.tls.certresolver=letsencrypt" + # # Jellyfin uses its own auth, so skip Authelia for internal + +# Additional volumes for your services +# volumes: +# linkding-data: +# filebrowser-data: +# jellyfin-config: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e3e8324 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,301 @@ +version: "3.8" + +# ============================================================================ +# LEMONSEC - Security Stack for LemonLink +# Deploy via Portainer: Git Repository +# URL: https://git.lemonlink.eu/impulsivefps/LemonSec +# ============================================================================ +# +# ENVIRONMENT VARIABLES (set in Portainer UI): +# Required: +# - CF_API_EMAIL Cloudflare account email +# - CF_API_KEY Cloudflare Global API Key +# - TRUENAS_IP TrueNAS Scale VM IP (e.g., 192.168.1.100) +# - TRUENAS_NEXTCLOUD_PORT TrueNAS Nextcloud port (e.g., 9001) +# +# Optional: +# - TZ Timezone (default: Europe/Stockholm) +# - CROWDSEC_API_KEY Generated after first CrowdSec start +# - TAILSCALE_IP For internal entrypoint binding +# +# Secrets (generate once, store securely): +# - AUTHELIA_JWT_SECRET +# - AUTHELIA_SESSION_SECRET +# - AUTHELIA_STORAGE_KEY +# +# ============================================================================ + +networks: + # External-facing network (Cloudflare → Traefik) + traefik-external: + driver: bridge + + # Internal network (Tailscale/VPN → Traefik) + traefik-internal: + driver: bridge + internal: true + + # Services network (isolated) + services: + driver: bridge + + # CrowdSec network + crowdsec: + driver: bridge + +volumes: + traefik-certs: + authelia-data: + crowdsec-data: + crowdsec-config: + adguard-work: + adguard-conf: + redis-data: + +services: + # ============================================================================ + # REVERSE PROXY - Traefik + # ============================================================================ + traefik: + image: traefik:v3.1 + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + cap_add: + - NET_BIND_SERVICE + networks: + - traefik-external + - traefik-internal + - services + - crowdsec + ports: + - "80:80" + - "443:443" + # Internal entrypoint - uncomment and set TAILSCALE_IP for Tailscale-only access + # - "${TAILSCALE_IP}:8443:8443" + environment: + - CF_API_EMAIL=${CF_API_EMAIL:?CF_API_EMAIL not set} + - CF_API_KEY=${CF_API_KEY:?CF_API_KEY not set} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik/traefik.yml:/traefik.yml:ro + - ./traefik/dynamic:/dynamic:ro + - traefik-certs:/letsencrypt + labels: + - "traefik.enable=true" + # Dashboard - INTERNAL ONLY + - "traefik.http.routers.traefik.rule=Host(`traefik.local.lemonlink.eu`)" + - "traefik.http.routers.traefik.entrypoints=websecure" + - "traefik.http.routers.traefik.service=api@internal" + - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" + - "traefik.http.routers.traefik.middlewares=authelia@docker" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:8082/ping"] + interval: 10s + timeout: 5s + retries: 3 + + # ============================================================================ + # SSO & AUTHENTICATION - Authelia + # ============================================================================ + authelia: + image: authelia/authelia:4.38 + container_name: authelia + restart: unless-stopped + networks: + - services + - traefik-external + - traefik-internal + expose: + - 9091 + volumes: + - authelia-data:/config + - ./authelia/configuration.yml:/configuration.yml:ro + - ./authelia/users_database.yml:/users_database.yml:ro + environment: + - TZ=${TZ:-Europe/Stockholm} + # Secrets - passed via environment variables for Portainer + - AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET:?AUTHELIA_JWT_SECRET not set} + - AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET:?AUTHELIA_SESSION_SECRET not set} + - AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_KEY:?AUTHELIA_STORAGE_KEY not set} + labels: + - "traefik.enable=true" + - "traefik.http.routers.authelia.rule=Host(`auth.lemonlink.eu`)" + - "traefik.http.routers.authelia.entrypoints=websecure" + - "traefik.http.routers.authelia.tls.certresolver=letsencrypt" + - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.lemonlink.eu/" + - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true" + - "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:9091/api/health"] + interval: 10s + timeout: 5s + retries: 3 + + # ============================================================================ + # THREAT DETECTION - CrowdSec + # ============================================================================ + crowdsec: + image: crowdsecurity/crowdsec:v1.6.0 + container_name: crowdsec + restart: unless-stopped + networks: + - crowdsec + - services + environment: + - TZ=${TZ:-Europe/Stockholm} + - COLLECTIONS=crowdsecurity/linux crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors + - GID=1000 + volumes: + - crowdsec-data:/var/lib/crowdsec/data + - crowdsec-config:/etc/crowdsec + - ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro + labels: + - "traefik.enable=false" + healthcheck: + test: ["CMD", "cscli", "machines", "list"] + interval: 30s + timeout: 10s + retries: 3 + + # CrowdSec bouncer for Traefik + crowdsec-bouncer-traefik: + image: crowdsecurity/traefik-bouncer:v0.1.0 + container_name: crowdsec-bouncer-traefik + restart: unless-stopped + networks: + - crowdsec + environment: + - CROWDSEC_BOUNCER_API_KEY=${CROWDSEC_API_KEY:-} + - CROWDSEC_AGENT_HOST=crowdsec:8080 + - CROWDSEC_BOUNCER_LOG_LEVEL=1 + labels: + - "traefik.enable=false" + depends_on: + - crowdsec + + # ============================================================================ + # INTERNAL DNS - AdGuard Home + # ============================================================================ + adguard: + image: adguard/adguardhome:v0.107.52 + container_name: adguard + restart: unless-stopped + networks: + - services + - traefik-internal + ports: + - "53:53/tcp" + - "53:53/udp" + expose: + - 3000 + - 80 + volumes: + - adguard-work:/opt/adguardhome/work + - adguard-conf:/opt/adguardhome/conf + labels: + - "traefik.enable=true" + - "traefik.http.routers.adguard.rule=Host(`dns.local.lemonlink.eu`)" + - "traefik.http.routers.adguard.entrypoints=websecure" + - "traefik.http.routers.adguard.service=adguard" + - "traefik.http.routers.adguard.tls.certresolver=letsencrypt" + - "traefik.http.routers.adguard.middlewares=authelia@docker" + - "traefik.http.services.adguard.loadbalancer.server.port=80" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:80"] + interval: 30s + timeout: 5s + retries: 3 + + # ============================================================================ + # CACHE - Redis (for Authelia sessions) + # ============================================================================ + redis: + image: redis:7-alpine + container_name: redis + restart: unless-stopped + networks: + - services + volumes: + - redis-data:/data + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + labels: + - "traefik.enable=false" + + # ============================================================================ + # EXTERNAL SERVICES ROUTING + # ============================================================================ + + # TrueNAS Nextcloud Router + # This container doesn't run anything - it's just configuration for Traefik + nextcloud-router: + image: alpine:latest + container_name: nextcloud-router + restart: "no" + entrypoint: ["echo", "Nextcloud routing configured"] + networks: + - services + labels: + - "traefik.enable=true" + + # Main Nextcloud route - EXTERNAL ACCESS + - "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)" + - "traefik.http.routers.nextcloud.entrypoints=websecure" + - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" + - "traefik.http.routers.nextcloud.service=nextcloud" + + # Point to TrueNAS Nextcloud + # Uses environment variables: TRUENAS_IP and TRUENAS_NEXTCLOUD_PORT + - "traefik.http.services.nextcloud.loadbalancer.server.url=http://${TRUENAS_IP:?TRUENAS_IP not set}:${TRUENAS_NEXTCLOUD_PORT:?TRUENAS_NEXTCLOUD_PORT not set}" + + # Security middlewares (NO Authelia - Nextcloud handles auth) + # This allows family to login directly to Nextcloud + - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file,nextcloud-headers" + + # Nextcloud-specific headers + - "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https" + - "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Frame-Options=SAMEORIGIN" + + # WebDAV/CalDAV routes (needed for mobile apps) + - "traefik.http.routers.nextcloud-dav.rule=Host(`cloud.lemonlink.eu`) && PathPrefix(`/.well-known/carddav`,`/.well-known/caldav`,`/remote.php`)" + - "traefik.http.routers.nextcloud-dav.entrypoints=websecure" + - "traefik.http.routers.nextcloud-dav.tls.certresolver=letsencrypt" + - "traefik.http.routers.nextcloud-dav.service=nextcloud" + + # TrueNAS Web UI Router (internal access only) + truenas-router: + image: alpine:latest + container_name: truenas-router + restart: "no" + entrypoint: ["echo", "TrueNAS routing configured"] + networks: + - services + labels: + - "traefik.enable=true" + - "traefik.http.routers.truenas.rule=Host(`nas.local.lemonlink.eu`)" + - "traefik.http.routers.truenas.entrypoints=websecure" + - "traefik.http.routers.truenas.tls.certresolver=letsencrypt" + - "traefik.http.routers.truenas.middlewares=authelia@docker" + - "traefik.http.services.truenas.loadbalancer.server.url=https://${TRUENAS_IP:?TRUENAS_IP not set}:443" + - "traefik.http.services.truenas.loadbalancer.serversTransport=insecureTransport@file" + + # ============================================================================ + # PORTAINER ROUTER (if Portainer is on host, not in this stack) + # ============================================================================ + portainer-router: + image: alpine:latest + container_name: portainer-router + restart: "no" + entrypoint: ["echo", "Portainer routing configured"] + networks: + - services + labels: + - "traefik.enable=true" + - "traefik.http.routers.portainer.rule=Host(`docker.local.lemonlink.eu`)" + - "traefik.http.routers.portainer.entrypoints=websecure" + - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" + - "traefik.http.routers.portainer.middlewares=authelia@docker" + - "traefik.http.services.portainer.loadbalancer.server.url=http://portainer:9000" diff --git a/docs/CLOUDFLARE.md b/docs/CLOUDFLARE.md new file mode 100644 index 0000000..33b15ab --- /dev/null +++ b/docs/CLOUDFLARE.md @@ -0,0 +1,173 @@ +# Cloudflare Configuration Guide + +## DNS Setup + +### 1. Create API Token (Recommended) + +1. Go to [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens) +2. Click **Create Token** +3. Use **Custom Token** +4. Configure permissions: + - **Zone:Read** - All zones + - **DNS:Edit** - All zones +5. Zone Resources: + - Include: Specific zone - lemonlink.eu +6. Click **Continue to summary** → **Create Token** +7. Copy the token to your `.env` file as `CF_DNS_API_TOKEN` + +### 2. DNS Records + +Create these A records in Cloudflare DNS: + +| Type | Name | Target | Proxy Status | TTL | +|------|------|--------|--------------|-----| +| A | @ | YOUR_SERVER_IP | 🟠 Proxied | Auto | +| A | * | YOUR_SERVER_IP | 🟠 Proxied | Auto | +| A | auth | YOUR_SERVER_IP | 🟠 Proxied | Auto | + +The wildcard (`*`) record is crucial for automatic subdomain routing. + +### 3. SSL/TLS Settings + +Navigate to **SSL/TLS** → **Overview**: + +| Setting | Value | Why | +|---------|-------|-----| +| SSL/TLS encryption mode | **Full (strict)** | Encrypts end-to-end with origin cert validation | +| Always Use HTTPS | **ON** | Redirects HTTP to HTTPS | +| Automatic HTTPS Rewrites | **ON** | Fixes mixed content | +| Minimum TLS Version | **1.2** | Security best practice | + +### 4. Edge Certificates + +**SSL/TLS** → **Edge Certificates**: + +- Enable **Always Use HTTPS** +- Set **Minimum TLS Version** to 1.2 +- Enable **Automatic HTTPS Rewrites** +- Enable **Opportunistic Encryption** +- Consider enabling **TLS 1.3** (fastest) + +### 5. Security Settings + +**Security** → **Settings**: + +| Setting | Recommended Value | +|---------|-------------------| +| Security Level | Medium or High | +| Challenge Passage | 30 minutes | +| Browser Integrity Check | ON | + +### 6. DDoS Protection + +**Security** → **DDoS** → **Configure**: + +- HTTP DDoS attack protection: **ON** +- Sensitivity: **High** +- Action: **Managed Challenge** + +### 7. Firewall Rules (Optional but Recommended) + +**Security** → **WAF** → **Firewall rules**: + +Create rules to block threats before they reach your server: + +**Block Known Bad Bots:** +``` +(cf.client.bot) or (http.user_agent contains "bot" and not cf.client.bot) +``` +Action: Block + +**Rate Limit Login Attempts:** +``` +(http.request.uri.path contains "/api/verify") or (http.request.uri.path contains "/login") +``` +Action: Challenge (Rate limit: 5 requests per 10 seconds) + +**Block Countries (Optional):** +``` +(not ip.geoip.country in {"SE" "NO" "DK" "FI" "DE"}) +``` +Action: Block or Challenge + +### 8. Origin Server Certificates (Optional) + +For extra security between Cloudflare and your server: + +1. **SSL/TLS** → **Origin Server** → **Create Certificate** +2. Let Cloudflare generate a certificate +3. Choose RSA (2048) or ECC +4. Download certificate and private key +5. Place in `traefik/certs/` and reference in config + +For most homelab setups, the auto-generated Let's Encrypt certificates are sufficient. + +### 9. Page Rules (Optional) + +**Rules** → **Page Rules**: + +Create rules for specific caching/security behaviors: + +**Cache Static Assets:** +``` +*lemonlink.eu/static/* +``` +Settings: +- Cache Level: Cache Everything +- Edge Cache TTL: 1 month + +**Secure Auth Endpoint:** +``` +auth.lemonlink.eu/* +``` +Settings: +- Security Level: High +- Browser Integrity Check: On + +### 10. Analytics & Monitoring + +Enable **Security Events** notifications: + +1. **Notifications** → **Add** +2. Choose **Security Events** +3. Set threshold (e.g., notify on 100+ events) + +## Verification + +Test your setup: + +```bash +# Check DNS resolution +dig +short lemonlink.eu +dig +short auth.lemonlink.eu +dig +short test.lemonlink.eu # Should resolve to CF IP + +# Check SSL +curl -I https://lemonlink.eu +# Should show Cloudflare headers + +# Check certificate +echo | openssl s_client -servername lemonlink.eu -connect lemonlink.eu:443 2>/dev/null | openssl x509 -noout -text | grep -A2 "Subject Alternative Name" +``` + +## Troubleshooting + +### "Too Many Redirects" Error +- Check SSL/TLS mode is **Full (strict)**, not Flexible +- Verify no redirect loops in Traefik config + +### Certificate Errors +- Ensure `CF_API_EMAIL` and `CF_API_KEY` are correct in `.env` +- Check Traefik logs for ACME errors +- Verify DNS propagation: `dig @1.1.1.1 lemonlink.eu` + +### Cloudflare IP in Logs (Not Client IP) +- Ensure `forwardedHeaders.trustedIPs` includes all Cloudflare IPs in `traefik.yml` +- Check `X-Forwarded-For` header is being passed + +## Cloudflare IPs (Keep Updated!) + +Traefik config includes Cloudflare IPs, but verify they're current: +https://www.cloudflare.com/ips/ + +Update `traefik/traefik.yml` if IPs change. diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..09041e4 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,278 @@ +# Security Hardening Guide + +## Overview + +This stack implements defense in depth with multiple security layers: + +1. **Network**: Cloudflare → Traefik → Service +2. **Authentication**: Authelia (SSO + 2FA) +3. **Authorization**: Access control rules +4. **Detection**: CrowdSec (IPS/IDS) +5. **Encryption**: TLS 1.3, strong ciphers +6. **Monitoring**: Logs, metrics, alerts + +## Pre-Deployment Checklist + +### 1. Server Hardening + +```bash +# Update system +sudo apt update && sudo apt upgrade -y + +# Install fail2ban +sudo apt install fail2ban + +# Configure fail2ban for SSH +cat < secrets/authelia_jwt_secret.txt + +# Session Secret (32+ chars) +openssl rand -hex 32 > secrets/authelia_session_secret.txt + +# Storage Encryption Key (32+ chars) +openssl rand -hex 32 > secrets/authelia_storage_key.txt + +# Authelia user password +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 \ + --password 'YourStrongPassword123!' +``` + +### 4. Cloudflare Security + +- [ ] SSL/TLS mode: Full (strict) +- [ ] Always Use HTTPS: ON +- [ ] Security Level: High +- [ ] Browser Integrity Check: ON +- [ ] Challenge Passage: 30 minutes +- [ ] Minimum TLS Version: 1.2 + +## Post-Deployment Security + +### 1. Verify CrowdSec is Working + +```bash +# Check CrowdSec is detecting attacks +docker-compose exec crowdsec cscli metrics + +# View active decisions (bans) +docker-compose exec crowdsec cscli decisions list + +# Simulate an attack to test +curl -I http://your-ip/.env +docker-compose exec crowdsec cscli decisions list +# Should show your IP banned +``` + +### 2. Configure Authelia Notifications + +Edit `authelia/configuration.yml`: + +```yaml +notifier: + smtp: + address: smtp.gmail.com:587 + username: your-email@gmail.com + password: ${SMTP_PASSWORD} + sender: "Authelia " +``` + +Test: Try logging in with wrong password 3 times → should get email. + +### 3. Enable CrowdSec Notifications + +```bash +# Install Discord/Slack plugin +docker-compose exec crowdsec cscli notifications add slack + +# Configure in CrowdSec container +docker-compose exec crowdsec sh -c "cat > /etc/crowdsec/notifications/slack.yaml" << 'EOF' +type: slack +name: slack_default +log_level: info +format: | + {{range . -}} + {{$alert := . -}} + {{range .Decisions -}} + :warning: *CrowdSec Alert* + *IP:* {{.Value}} + *Reason:* {{.Scenario}} + *Duration:* {{.Duration}} + {{end -}} + {{end -}} +webhook: https://hooks.slack.com/services/YOUR/WEBHOOK/URL +EOF +``` + +### 4. Regular Security Tasks + +**Weekly:** +```bash +# Check for new decisions +docker-compose exec crowdsec cscli decisions list + +# Review failed logins +docker-compose logs authelia | grep "unsuccessful" + +# Update images +docker-compose pull +docker-compose up -d +``` + +**Monthly:** +```bash +# Rotate secrets +./scripts/rotate-secrets.sh # You need to create this + +# Review access logs +docker-compose logs traefik | grep -E "(401|403|429)" + +# Backup +tar czf backup-$(date +%Y%m%d).tar.gz traefik/ authelia/ crowdsec/ .env +``` + +## Threat Model + +### What This Stack Protects Against + +| Threat | Mitigation | +|--------|-----------| +| Credential stuffing | Authelia rate limiting, fail2ban | +| Brute force attacks | CrowdSec detection, progressive delays | +| DDoS | Cloudflare, rate limiting | +| MITM | TLS 1.3, HSTS, certificate pinning | +| Session hijacking | Secure cookies, short expiration | +| XSS/CSRF | Security headers, SameSite cookies | +| SQL injection | Parameterized queries (app responsibility) | + +### What It Doesn't Protect Against + +- Zero-day vulnerabilities in applications +- Social engineering +- Compromised client devices +- Insider threats +- Application-level logic bugs + +## Incident Response + +### If You Suspect a Breach + +1. **Isolate** + ```bash + # Stop all services + docker-compose down + + # Block all traffic temporarily + sudo ufw default deny incoming + ``` + +2. **Investigate** + ```bash + # Check logs + docker-compose logs > incident-$(date +%Y%m%d).log + + # Check CrowdSec decisions + docker-compose exec crowdsec cscli decisions list + + # Check for unknown containers + docker ps -a + ``` + +3. **Recover** + ```bash + # Rotate all secrets + # Change all passwords + # Re-deploy from clean backup + ``` + +## Security Testing + +### Automated Scans + +```bash +# Test TLS configuration +docker run --rm drwetter/testssl.sh https://lemonlink.eu + +# Test headers +curl -I https://lemonlink.eu | grep -E "(strict-transport-security|content-security-policy|x-frame-options)" + +# Test rate limiting +for i in {1..20}; do curl -s -o /dev/null -w "%{http_code}" https://auth.lemonlink.eu/; done +``` + +### Manual Penetration Testing + +1. Try accessing internal services from external IP +2. Attempt SQL injection in login forms +3. Test XSS payloads +4. Verify 2FA can't be bypassed +5. Check for information disclosure in headers + +## Compliance Notes + +This setup helps with: +- **GDPR**: Encryption, access logs, data retention +- **ISO 27001**: Defense in depth, monitoring +- **SOC 2**: Audit trails, access controls + +But does NOT make you compliant by itself. You must: +- Document all data flows +- Implement data retention policies +- Conduct regular audits +- Train users on security + +## Additional Resources + +- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/) +- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/) +- [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker) +- [Cloudflare Security Center](https://dash.cloudflare.com/?to=/:account/:zone/security) diff --git a/docs/TAILSCALE.md b/docs/TAILSCALE.md new file mode 100644 index 0000000..000262f --- /dev/null +++ b/docs/TAILSCALE.md @@ -0,0 +1,173 @@ +# Tailscale Integration + +Tailscale provides secure, zero-config VPN access to your internal network without exposing ports. + +## Setup + +### 1. Install Tailscale on Server + +```bash +# On Proxmox (Debian/Ubuntu) +curl -fsSL https://tailscale.com/install.sh | sh + +# Start Tailscale +sudo tailscale up + +# Get your Tailscale IP +sudo tailscale ip -4 +``` + +### 2. Configure DNS in Tailscale Admin + +1. Go to [Tailscale Admin Console](https://login.tailscale.com/admin/dns) +2. Add **Nameservers**: + - Your Raspberry Pi Tailscale IP (for AdGuard) + - Or: `100.100.100.100` (Tailscale's MagicDNS) +3. Enable **Override local DNS** (optional) +4. Add **Search domain**: `local.lemonlink.eu` + +### 3. DNS Split Horizon + +Configure AdGuard to handle `local.lemonlink.eu`: + +``` +# In AdGuard Home (dns.local.lemonlink.eu) +# Filters → DNS rewrites + +*.local.lemonlink.eu → YOUR_TAILSCALE_IP +``` + +### 4. Traefik Internal Entrypoint + +The internal entrypoint (port 8443) is configured to only listen on Tailscale: + +```yaml +# In docker-compose.yml, under traefik service: +ports: + - "${TAILSCALE_IP}:8443:8443" # Only accessible via Tailscale +``` + +Update `.env`: +``` +TAILSCALE_IP=100.x.x.x # Your server's Tailscale IP +``` + +### 5. ACLs (Access Control Lists) + +For extra security, configure Tailscale ACLs: + +```json +// In Tailscale Admin → Access Controls +{ + "acls": [ + // Allow users to access specific ports + { + "action": "accept", + "src": ["group:family"], + "dst": ["100.x.x.x:443,8443"] // Your server + }, + // Deny everything else + { + "action": "deny", + "src": ["*"], + "dst": ["100.x.x.x:*"] + } + ] +} +``` + +## Testing + +```bash +# From your phone/computer with Tailscale + +# Test internal DNS +ping traefik.local.lemonlink.eu + +# Access internal services +curl https://traefik.local.lemonlink.eu:8443 + +# Verify you're going through Tailscale +# Should show 100.x.x.x IPs, not public IPs +traceroute traefik.local.lemonlink.eu +``` + +## Raspberry Pi 5 Setup + +Your Raspberry Pi can run additional monitoring services: + +```yaml +# On Raspberry Pi - docker-compose.yml +version: "3.8" + +services: + adguard: + image: adguard/adguardhome:v0.107.52 + ports: + - "53:53/tcp" + - "53:53/udp" + - "3000:3000" # Initial setup only + volumes: + - ./adguard-work:/opt/adguardhome/work + - ./adguard-conf:/opt/adguardhome/conf + restart: unless-stopped +``` + +Configure AdGuard: +1. Bootstrap DNS: `1.1.1.1`, `8.8.8.8` +2. Upstream DNS: `https://dns.cloudflare.com/dns-query` +3. DNS rewrites for local domains + +## Security Best Practices + +1. **Disable key expiry** for servers: + ```bash + sudo tailscale up --reset --operator=$USER + ``` + +2. **Enable device approval** for new devices + +3. **Use ACLs** to limit access between devices + +4. **Enable HTTPS** (Beta feature): + ```bash + sudo tailscale up --accept-routes + sudo tailscale cert your-host.local.lemonlink.eu + ``` + +5. **Disable subnet routing** if not needed: + ```bash + sudo tailscale up --accept-routes=false + ``` + +## Troubleshooting + +### Can't resolve local.lemonlink.eu +- Check AdGuard is running on Raspberry Pi +- Verify Tailscale DNS settings +- Test: `dig @100.x.x.x traefik.local.lemonlink.eu` (Raspberry Pi IP) + +### Connection refused on :8443 +- Verify Traefik is bound to Tailscale IP +- Check firewall: `sudo ufw allow from 100.64.0.0/10 to any port 8443` +- Test locally: `curl -k https://localhost:8443` + +### Slow performance +- Enable NAT optimization: + ```bash + echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf + sudo sysctl -p + ``` +- Use `--netfilter-mode=off` if issues persist + +## Comparison: External vs Internal Access + +| Service | External URL | Internal URL | Auth | +|---------|-------------|--------------|------| +| Nextcloud | cloud.lemonlink.eu | cloud.local.lemonlink.eu:8443 | Authelia 2FA | +| Vaultwarden | vault.lemonlink.eu | vault.local.lemonlink.eu:8443 | Authelia 2FA | +| Portainer | - | docker.local.lemonlink.eu:8443 | Authelia 1FA | +| Traefik Dashboard | - | traefik.local.lemonlink.eu:8443 | Authelia 1FA | +| AdGuard | - | dns.local.lemonlink.eu:8443 | Authelia 1FA | + +*1FA = Username/Password, 2FA = + TOTP/WebAuthn* diff --git a/docs/TRUENAS-NEXTCLOUD.md b/docs/TRUENAS-NEXTCLOUD.md new file mode 100644 index 0000000..d746007 --- /dev/null +++ b/docs/TRUENAS-NEXTCLOUD.md @@ -0,0 +1,280 @@ +# TrueNAS Scale + Nextcloud Integration + +## Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ +│ Proxmox VM │ │ TrueNAS Scale │ +│ (LemonSec) │◄───────►│ VM │ +│ │ │ │ +│ ┌─────────────┐ │ │ ┌──────────────┐ │ +│ │ Traefik │ │ HTTP │ │ Nextcloud │ │ +│ │ (443) │◄├─────────┤►│ App │ │ +│ └─────────────┘ │ │ └──────────────┘ │ +│ │ │ └──────────────────┘ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ Authelia │ │ (Optional SSO) +│ └─────────────┘ │ +└─────────────────┘ +``` + +## Step 1: Configure TrueNAS Nextcloud + +### In TrueNAS Scale Web UI: + +1. **Apps** → **Available Applications** → **Nextcloud** +2. **Install** with these settings: + - **Application Name**: `nextcloud` + - **Nextcloud Config**: + - **Nextcloud URL**: `https://cloud.lemonlink.eu` (your domain) + - **Username**: `admin` + - **Password**: Generate strong password + - **Networking**: + - **Web Port**: `9001` (or any free port) + - **Storage**: + - Set up your data pool + - **Enable**: True + +3. **Wait** for installation to complete + +4. **Note the Node Port**: TrueNAS will show the port (e.g., `9001`) + +## Step 2: Configure LemonSec + +### Update .env + +```bash +# Edit .env +TRUENAS_IP=192.168.1.100 # Your TrueNAS IP +TRUENAS_NEXTCLOUD_PORT=9001 # Nextcloud port on TrueNAS +``` + +### Choose Authentication Mode + +Edit `docker-compose.external.yml` and pick your authentication: + +#### Option A: Nextcloud Native Auth (Recommended for Families) +- Family members use Nextcloud accounts directly +- No Authelia barrier +- Nextcloud handles its own security + +```yaml +labels: + # No authelia middleware + - "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file" +``` + +#### Option B: Authelia + Nextcloud (Double Security) +- Authelia login first, then Nextcloud login +- Good for admin accounts + +```yaml +labels: + - "traefik.http.routers.nextcloud.middlewares=authelia@docker,security-headers@file,rate-limit@file" +``` + +#### Option C: Authelia SSO (One login for both) +- Authelia handles auth, passes to Nextcloud +- Requires OpenID Connect setup in Nextcloud + +See "Advanced: SSO Integration" below. + +## Step 3: Deploy + +```bash +# Start core LemonSec +docker-compose up -d + +# Start external routing +docker-compose -f docker-compose.yml -f docker-compose.external.yml up -d +``` + +## Step 4: Configure Nextcloud + +### First Login + +1. Go to `https://cloud.lemonlink.eu` +2. Login with TrueNAS Nextcloud admin credentials + +### Required Settings + +Add to TrueNAS Nextcloud **config.php**: + +```php +'trusted_domains' => +array ( + 0 => 'localhost', + 1 => '192.168.1.100', # TrueNAS IP + 2 => 'cloud.lemonlink.eu', # Your domain +), +'overwriteprotocol' => 'https', +'overwritehost' => 'cloud.lemonlink.eu', +'overwrite.cli.url' => 'https://cloud.lemonlink.eu', +'trusted_proxies' => +array ( + 0 => '192.168.1.50', # Proxmox VM IP (Traefik) +), +``` + +In TrueNAS: +1. **Apps** → **Nextcloud** → **Edit** → **Nextcloud Config** +2. Add to **Additional Environments**: + ``` + OVERWRITEPROTOCOL=https + OVERWRITEHOST=cloud.lemonlink.eu + TRUSTED_PROXIES=192.168.1.50 + ``` + +### Fix WebDAV/Calendar Sync + +Create file `fix-wellknown.yml` in Traefik dynamic config: + +```yaml +http: + middlewares: + nextcloud-wellknown: + redirectRegex: + regex: "^https://(.*)/.well-known/(card|cal)dav" + replacement: "https://cloud.lemonlink.eu/remote.php/dav/" + permanent: true +``` + +Add middleware to Nextcloud router: +```yaml +- "traefik.http.routers.nextcloud.middlewares=...,nextcloud-wellknown" +``` + +## Step 5: Family Access + +### Create Family Accounts + +1. Login as Nextcloud admin +2. **Settings** → **Users** → **Create user** +3. Create accounts for each family member + +### Share the URL + +Tell your family: +- **URL**: `https://cloud.lemonlink.eu` +- **Login**: Their individual username/password +- **Apps**: Web, Desktop, Mobile apps available + +### Mobile/Desktop Setup + +**iOS/Android:** +1. Download Nextcloud app +2. Enter server: `https://cloud.lemonlink.eu` +3. Login with credentials + +**Desktop:** +1. Download from nextcloud.com/install +2. Same server URL +3. Enable auto-sync + +## Troubleshooting + +### "Access through untrusted domain" + +Add your domain to TrueNAS Nextcloud config: +```bash +# Shell into TrueNAS Nextcloud pod +k3s kubectl exec -it -n ix-nextcloud nextcloud-xxx -- /bin/sh + +# Edit config +cd /var/www/html/config +vi config.php +# Add 'cloud.lemonlink.eu' to trusted_domains +``` + +### Slow Uploads + +Increase timeouts in Traefik: +```yaml +labels: + - "traefik.http.services.nextcloud.loadbalancer.responseforwarding.flushinterval=100ms" +``` + +### Large File Uploads (Nextcloud default limit) + +In TrueNAS Nextcloud: +1. **Apps** → **Nextcloud** → **Edit** +2. **PHP Configuration**: + - **Upload Max Size**: `10G` (or your limit) + - **Memory Limit**: `1G` + +### HTTPS Redirect Loop + +Ensure these are set in Nextcloud: +```php +'overwriteprotocol' => 'https', +'trusted_proxies' => ['192.168.1.50'], +``` + +## Advanced: Authelia SSO Integration + +If you want single sign-on (login once, access Nextcloud without second login): + +### 1. Configure Authelia OpenID Connect + +Add to `authelia/configuration.yml`: + +```yaml +identity_providers: + oidc: + clients: + - client_id: nextcloud + client_name: Nextcloud + client_secret: ${NEXTCLOUD_OAUTH_SECRET} # Generate with openssl rand -hex 32 + public: false + authorization_policy: one_factor + require_pkce: true + pkce_challenge_method: S256 + redirect_uris: + - https://cloud.lemonlink.eu/apps/user_oidc/code + scopes: + - openid + - profile + - email + - groups +``` + +### 2. Install OIDC App in Nextcloud + +1. Nextcloud → **Apps** → **Search**: "OpenID Connect user backend" +2. **Enable** +3. **Settings** → **Administration** → **OpenID Connect** +4. Configure: + - **Provider name**: Authelia + - **Client ID**: nextcloud + - **Client Secret**: (from above) + - **Discovery endpoint**: `https://auth.lemonlink.eu/.well-known/openid-configuration` + - **Scope**: `openid profile email groups` + - **User ID mapping**: `preferred_username` + +### 3. Optional: Disable Authelia on Nextcloud route + +Since Nextcloud now handles auth via Authelia OIDC: +```yaml +# Remove authelia@docker middleware +- "traefik.http.routers.nextcloud.middlewares=security-headers@file,rate-limit@file" +``` + +## Migration from NPM + +If you have existing NPM configuration: + +1. **Export NPM config**: Settings → Export +2. **Note custom locations**: Proxy Hosts → Edit → Advanced +3. **Recreate in Traefik**: + - Each NPM Proxy Host = One Traefik router + - NPM Advanced config = Traefik middlewares +4. **Test** one service at a time +5. **Disable NPM** only after everything works + +## Security Notes + +- TrueNAS Nextcloud should not be exposed directly (no port forward to TrueNAS) +- All traffic goes through Traefik (single entry point) +- Consider fail2ban on TrueNAS for extra protection +- Regular Nextcloud updates via TrueNAS UI diff --git a/examples/internal-service-compose.yml b/examples/internal-service-compose.yml new file mode 100644 index 0000000..c148a2a --- /dev/null +++ b/examples/internal-service-compose.yml @@ -0,0 +1,32 @@ +# Example: Internal-only service (Portainer) +# Accessible only via Tailscale/VPN + +version: "3.8" + +networks: + services: + external: true + +volumes: + portainer-data: + +services: + portainer: + image: portainer/portainer-ce:latest + container_name: portainer + restart: unless-stopped + networks: + - services + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - portainer-data:/data + labels: + - "traefik.enable=true" + # Internal entrypoint only - NOT exposed to internet + - "traefik.http.routers.portainer.rule=Host(`docker.local.lemonlink.eu`)" + - "traefik.http.routers.portainer.entrypoints=internal" + - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" + # Optional: Skip Authelia for Portainer if it has its own auth + # Or keep it for extra security + - "traefik.http.routers.portainer.middlewares=authelia@docker" + - "traefik.http.services.portainer.loadbalancer.server.port=9000" diff --git a/examples/nextcloud-compose.yml b/examples/nextcloud-compose.yml new file mode 100644 index 0000000..15303d4 --- /dev/null +++ b/examples/nextcloud-compose.yml @@ -0,0 +1,61 @@ +# Example: Nextcloud with LemonSec +# Add this to your docker-compose.yml or run separately + +version: "3.8" + +networks: + services: + external: true # Use the existing network from main compose + +volumes: + nextcloud-data: + nextcloud-db: + +services: + nextcloud-db: + image: mariadb:10.11 + container_name: nextcloud-db + restart: unless-stopped + networks: + - services + environment: + - MYSQL_ROOT_PASSWORD=${NEXTCLOUD_MYSQL_ROOT_PASSWORD} + - MYSQL_PASSWORD=${NEXTCLOUD_MYSQL_PASSWORD} + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + volumes: + - nextcloud-db:/var/lib/mysql + labels: + - "traefik.enable=false" + + nextcloud: + image: nextcloud:29-apache + container_name: nextcloud + restart: unless-stopped + networks: + - services + environment: + - MYSQL_PASSWORD=${NEXTCLOUD_MYSQL_PASSWORD} + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_HOST=nextcloud-db + - NEXTCLOUD_TRUSTED_DOMAINS=cloud.lemonlink.eu + - OVERWRITEPROTOCOL=https + - OVERWRITEHOST=cloud.lemonlink.eu + - OVERWRITECLIURL=https://cloud.lemonlink.eu + volumes: + - nextcloud-data:/var/www/html + depends_on: + - nextcloud-db + labels: + - "traefik.enable=true" + # External access + - "traefik.http.routers.nextcloud.rule=Host(`cloud.lemonlink.eu`)" + - "traefik.http.routers.nextcloud.entrypoints=websecure" + - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" + - "traefik.http.routers.nextcloud.middlewares=authelia@docker,nextcloud-dav" + - "traefik.http.services.nextcloud.loadbalancer.server.port=80" + # Nextcloud DAV fix + - "traefik.http.middlewares.nextcloud-dav.redirectregex.permanent=true" + - "traefik.http.middlewares.nextcloud-dav.redirectregex.regex=^https://(.*)/.well-known/(card|cal)dav" + - "traefik.http.middlewares.nextcloud-dav.redirectregex.replacement=https://$${1}/remote.php/dav/" diff --git a/examples/vaultwarden-compose.yml b/examples/vaultwarden-compose.yml new file mode 100644 index 0000000..f71cf80 --- /dev/null +++ b/examples/vaultwarden-compose.yml @@ -0,0 +1,57 @@ +# Example: Vaultwarden (Bitwarden RS) with LemonSec + +version: "3.8" + +networks: + services: + external: true + +volumes: + vaultwarden-data: + +services: + vaultwarden: + image: vaultwarden/server:latest + container_name: vaultwarden + restart: unless-stopped + networks: + - services + environment: + - WEBSOCKET_ENABLED=true + - SIGNUPS_ALLOWED=false # Disable after creating your account + - ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN} + - DOMAIN=https://vault.lemonlink.eu + - SMTP_HOST=${SMTP_HOST} + - SMTP_FROM=${SMTP_FROM} + - SMTP_PORT=587 + - SMTP_SECURITY=starttls + - SMTP_USERNAME=${SMTP_USERNAME} + - SMTP_PASSWORD=${SMTP_PASSWORD} + volumes: + - vaultwarden-data:/data + labels: + - "traefik.enable=true" + + # Main Vaultwarden interface + - "traefik.http.routers.vaultwarden.rule=Host(`vault.lemonlink.eu`)" + - "traefik.http.routers.vaultwarden.entrypoints=websecure" + - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt" + - "traefik.http.routers.vaultwarden.service=vaultwarden" + - "traefik.http.routers.vaultwarden.middlewares=authelia@docker" + + # WebSocket for real-time sync + - "traefik.http.routers.vaultwarden-ws.rule=Host(`vault.lemonlink.eu`) && Path(`/notifications/hub`)" + - "traefik.http.routers.vaultwarden-ws.entrypoints=websecure" + - "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt" + - "traefik.http.routers.vaultwarden-ws.service=vaultwarden-ws" + + # Admin panel (separate router for different middleware) + - "traefik.http.routers.vaultwarden-admin.rule=Host(`vault.lemonlink.eu`) && PathPrefix(`/admin`)" + - "traefik.http.routers.vaultwarden-admin.entrypoints=websecure" + - "traefik.http.routers.vaultwarden-admin.tls.certresolver=letsencrypt" + - "traefik.http.routers.vaultwarden-admin.service=vaultwarden" + - "traefik.http.routers.vaultwarden-admin.middlewares=authelia@docker,rate-limit-strict@file" + + # Services + - "traefik.http.services.vaultwarden.loadbalancer.server.port=80" + - "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012" diff --git a/monitoring/loki-config.yml b/monitoring/loki-config.yml new file mode 100644 index 0000000..aa62eb9 --- /dev/null +++ b/monitoring/loki-config.yml @@ -0,0 +1,40 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + +common: + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + instance_addr: 127.0.0.1 + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 + +schema_config: + configs: + - from: 2020-10-24 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 24h + +ruler: + alertmanager_url: http://localhost:9093 + +analytics: + reporting_enabled: false diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml new file mode 100644 index 0000000..d8f2401 --- /dev/null +++ b/monitoring/prometheus.yml @@ -0,0 +1,26 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +alerting: + alertmanagers: [] + +rule_files: [] + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'traefik' + static_configs: + - targets: ['traefik:8080'] + metrics_path: /metrics + + - job_name: 'docker' + static_configs: + - targets: ['cadvisor:8080'] + + - job_name: 'node-exporter' + static_configs: + - targets: ['node-exporter:9100'] diff --git a/monitoring/promtail-config.yml b/monitoring/promtail-config.yml new file mode 100644 index 0000000..6ae9e4a --- /dev/null +++ b/monitoring/promtail-config.yml @@ -0,0 +1,35 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3100/loki/api/v1/push + +scrape_configs: + - job_name: traefik + static_configs: + - targets: + - localhost + labels: + job: traefik + __path__: /var/log/traefik/*.log + pipeline_stages: + - json: + expressions: + timestamp: time + level: level + msg: msg + - timestamp: + source: timestamp + format: RFC3339 + + - job_name: syslog + static_configs: + - targets: + - localhost + labels: + job: syslog + __path__: /var/log/auth.log diff --git a/setup.ps1 b/setup.ps1 new file mode 100644 index 0000000..d4af75c --- /dev/null +++ b/setup.ps1 @@ -0,0 +1,93 @@ +#!/usr/bin/env pwsh +# LemonSec Setup Script +# Run this script to initialize the security stack + +$ErrorActionPreference = "Stop" + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " LemonSec Security Stack Setup" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# Check if running as administrator (not required for Docker Desktop) +# if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { +# Write-Warning "This script should be run as Administrator for some features." +# } + +# Create necessary directories +Write-Host "[1/7] Creating directories..." -ForegroundColor Green +$dirs = @( + "traefik/logs", + "secrets", + "crowdsec-data", + "uptime-kuma-data" +) +foreach ($dir in $dirs) { + if (!(Test-Path $dir)) { + New-Item -ItemType Directory -Path $dir -Force | Out-Null + } +} + +# Generate secrets +Write-Host "[2/7] Generating secrets..." -ForegroundColor Green + +function Generate-Secret { + $bytes = New-Object byte[] 32 + $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() + $rng.GetBytes($bytes) + return [BitConverter]::ToString($bytes).Replace("-", "").ToLower() +} + +if (!(Test-Path "secrets/authelia_jwt_secret.txt")) { + Generate-Secret | Set-Content -Path "secrets/authelia_jwt_secret.txt" -NoNewline + Write-Host " ✓ Created authelia_jwt_secret.txt" -ForegroundColor Gray +} + +if (!(Test-Path "secrets/authelia_session_secret.txt")) { + Generate-Secret | Set-Content -Path "secrets/authelia_session_secret.txt" -NoNewline + Write-Host " ✓ Created authelia_session_secret.txt" -ForegroundColor Gray +} + +if (!(Test-Path "secrets/authelia_storage_key.txt")) { + Generate-Secret | Set-Content -Path "secrets/authelia_storage_key.txt" -NoNewline + Write-Host " ✓ Created authelia_storage_key.txt" -ForegroundColor Gray +} + +# Set permissions (Windows doesn't have the same permission model, but we can set ACLs) +Write-Host "[3/7] Setting permissions..." -ForegroundColor Green +# Note: On Windows, Docker Desktop handles permissions differently + +# Check if .env exists +Write-Host "[4/7] Checking configuration..." -ForegroundColor Green +if (!(Test-Path ".env")) { + Write-Host " ⚠ .env file not found!" -ForegroundColor Yellow + Write-Host " Copy .env.example to .env and fill in your values:" -ForegroundColor Yellow + Write-Host " cp .env.example .env" -ForegroundColor Yellow + Write-Host " nano .env # or your preferred editor" -ForegroundColor Yellow + exit 1 +} + +# Pull images +Write-Host "[5/7] Pulling Docker images..." -ForegroundColor Green +docker-compose pull + +# Create external network if it doesn't exist +Write-Host "[6/7] Setting up Docker networks..." -ForegroundColor Green +$networks = docker network ls --format "{{.Name}}" +if ($networks -notcontains "traefik-external") { + docker network create traefik-external +} + +Write-Host "[7/7] Setup complete!" -ForegroundColor Green +Write-Host "" +Write-Host "Next steps:" -ForegroundColor Cyan +Write-Host " 1. Ensure .env is configured with your Cloudflare credentials" -ForegroundColor White +Write-Host " 2. Update authelia/users_database.yml with your users" -ForegroundColor White +Write-Host " 3. Start the stack: docker-compose up -d" -ForegroundColor White +Write-Host " 4. Check logs: docker-compose logs -f traefik" -ForegroundColor White +Write-Host " 5. Generate CrowdSec API key: docker-compose exec crowdsec cscli bouncers add traefik-bouncer" -ForegroundColor White +Write-Host " 6. Add the key to .env and restart: docker-compose up -d" -ForegroundColor White +Write-Host "" +Write-Host "Access points:" -ForegroundColor Cyan +Write-Host " - External: https://auth.lemonlink.eu (after DNS setup)" -ForegroundColor White +Write-Host " - Internal: https://traefik.local.lemonlink.eu:8443 (via Tailscale)" -ForegroundColor White diff --git a/stack.env b/stack.env new file mode 100644 index 0000000..84d06a7 --- /dev/null +++ b/stack.env @@ -0,0 +1,56 @@ +# LemonSec Environment Variables for Portainer +# Copy these into Portainer's Environment Variables section when deploying + +# ============================================================================ +# REQUIRED - Cloudflare DNS +# ============================================================================ +# Your Cloudflare account email +CF_API_EMAIL=your-email@example.com + +# Cloudflare Global API Key (from https://dash.cloudflare.com/profile/api-tokens) +# Note: DNS API Token can also be used as CF_DNS_API_TOKEN +CF_API_KEY=your-cloudflare-global-api-key + +# ============================================================================ +# REQUIRED - TrueNAS Configuration +# ============================================================================ +# IP address of your TrueNAS Scale VM +TRUENAS_IP=192.168.1.100 + +# Port where Nextcloud is exposed on TrueNAS +# Check: TrueNAS → Apps → Nextcloud → Node Port +TRUENAS_NEXTCLOUD_PORT=9001 + +# ============================================================================ +# REQUIRED - Authelia Secrets +# Generate these with: openssl rand -hex 32 +# Or use the setup script: ./setup.ps1 (on Windows) +# ============================================================================ +AUTHELIA_JWT_SECRET=change-me-generate-with-openssl-rand-hex-32 +AUTHELIA_SESSION_SECRET=change-me-generate-with-openssl-rand-hex-32 +AUTHELIA_STORAGE_KEY=change-me-generate-with-openssl-rand-hex-32 + +# ============================================================================ +# OPTIONAL - CrowdSec +# Generate after first deployment: +# docker exec crowdsec cscli bouncers add traefik-bouncer +# Then add the key here and redeploy +# ============================================================================ +CROWDSEC_API_KEY= + +# ============================================================================ +# OPTIONAL - General Settings +# ============================================================================ +# Timezone +TZ=Europe/Stockholm + +# Tailscale IP (for internal entrypoint binding) +# Get with: tailscale ip -4 +TAILSCALE_IP= + +# ============================================================================ +# OPTIONAL - Grafana (if using monitoring profile) +# ============================================================================ +# GRAFANA_ADMIN_USER=admin +# GRAFANA_ADMIN_PASSWORD=change-me +# GRAFANA_OAUTH_SECRET=generate-with-authelia diff --git a/traefik/dynamic/middlewares.yml b/traefik/dynamic/middlewares.yml new file mode 100644 index 0000000..e7084d4 --- /dev/null +++ b/traefik/dynamic/middlewares.yml @@ -0,0 +1,128 @@ +http: + middlewares: + # ============================================================================ + # SECURITY MIDDLEWARES + # ============================================================================ + + # Security Headers - Comprehensive hardening + security-headers: + headers: + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 31536000 # 1 year + customFrameOptionsValue: "SAMEORIGIN" + contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" + customResponseHeaders: + X-Robots-Tag: "noindex, nofollow" + X-Download-Options: "noopen" + X-Permitted-Cross-Domain-Policies: "none" + Permissions-Policy: "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" + Referrer-Policy: "strict-origin-when-cross-origin" + sslProxyHeaders: + X-Forwarded-Proto: https + + # Rate Limiting - External + rate-limit: + rateLimit: + average: 100 + burst: 200 + period: 1m + + # Rate Limiting - Internal (more lenient) + rate-limit-internal: + rateLimit: + average: 300 + burst: 500 + period: 1m + + # Rate Limiting - Strict (for auth endpoints) + rate-limit-strict: + rateLimit: + average: 10 + burst: 20 + period: 1m + + # Geo-blocking (optional - configure countries as needed) + geo-block: + plugin: + geo-block: + allowLocalRequests: true + logLocalRequests: false + logAllowedRequests: false + logApiRequests: true + api: "https://get.geojs.io/v1/ip/country/{ip}" + apiTimeoutMs: 150 + cacheSize: 1000 + forceMonthlyUpdate: true + allowUnknownCountries: false + unknownCountryApiResponse: "nil" + blackListMode: false + # Countries allowed (ISO 3166-1 alpha-2) + countries: + - SE # Sweden + - NO # Norway + - DK # Denmark + - FI # Finland + # Add more as needed + + # Compress responses + compress: + compress: + excludedContentTypes: + - text/event-stream + + # Redirect to HTTPS (fallback) + redirect-https: + redirectScheme: + scheme: https + permanent: true + + # Add X-Forwarded headers + forwarded-headers: + headers: + customRequestHeaders: + X-Forwarded-Port: "443" + sslProxyHeaders: + X-Forwarded-Proto: https + + # Real IP from Cloudflare + cloudflare-real-ip: + plugin: + cloudflarewarp: + disableDefault: false + + # ============================================================================ + # AUTHENTICATION MIDDLEWARES + # ============================================================================ + + # Authelia forward auth (from Docker labels) + authelia: + forwardAuth: + address: "http://authelia:9091/api/verify?rd=https://auth.lemonlink.eu/" + trustForwardHeader: true + authResponseHeaders: + - Remote-User + - Remote-Groups + - Remote-Name + - Remote-Email + + # Basic Auth fallback (for emergencies) + basic-auth: + basicAuth: + users: + # Generate with: htpasswd -nb admin yourpassword | sed -e s/\$/\$\$/g + # Replace $ with $$ in the hash for YAML + - "admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/" # admin:admin - CHANGE THIS! + realm: "LemonLink Secured" + + # ============================================================================ + # CROWDSEC MIDDLEWARE + # ============================================================================ + + crowdsec-bouncer: + forwardAuth: + address: http://crowdsec-bouncer-traefik:8080/api/v1/forwardAuth + trustForwardHeader: true diff --git a/traefik/dynamic/tls.yml b/traefik/dynamic/tls.yml new file mode 100644 index 0000000..f005ffb --- /dev/null +++ b/traefik/dynamic/tls.yml @@ -0,0 +1,44 @@ +tls: + options: + # Modern TLS configuration + modern: + minVersion: VersionTLS13 + cipherSuites: [] + curvePreferences: + - X25519 + - P-256 + - P-384 + + # Intermediate TLS configuration (better compatibility) + intermediate: + minVersion: VersionTLS12 + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + curvePreferences: + - X25519 + - P-256 + - P-384 + sniStrict: true + + # Default options + default: + minVersion: VersionTLS12 + sniStrict: false + + certificates: + # Wildcard certificate for local domains + - certFile: /letsencrypt/local.lemonlink.eu.crt + keyFile: /letsencrypt/local.lemonlink.eu.key + stores: + - default + + stores: + default: + defaultCertificate: + certFile: /letsencrypt/local.lemonlink.eu.crt + keyFile: /letsencrypt/local.lemonlink.eu.key diff --git a/traefik/traefik.yml b/traefik/traefik.yml new file mode 100644 index 0000000..0c98d99 --- /dev/null +++ b/traefik/traefik.yml @@ -0,0 +1,163 @@ +global: + checkNewVersion: false + sendAnonymousUsage: false + +api: + dashboard: true + debug: false + # Secure the API + insecure: false + +ping: + entryPoint: "ping" + +log: + level: INFO + filePath: /var/log/traefik/traefik.log + format: json + +accessLog: + filePath: /var/log/traefik/access.log + format: json + bufferingSize: 100 + filters: + statusCodes: + - "200-299" # Success + - "300-399" # Redirects + - "400-599" # Errors + retryAttempts: true + minDuration: 10ms + fields: + headers: + defaultMode: keep + names: + User-Agent: keep + Authorization: drop + Cookie: drop + X-Forwarded-For: keep + X-Real-Ip: keep + +entryPoints: + # HTTP - Redirect to HTTPS + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + scheme: https + permanent: true + priority: 100 + + # HTTPS External (Cloudflare) + websecure: + address: ":443" + http: + tls: + certResolver: letsencrypt + domains: + - main: "lemonlink.eu" + sans: + - "*.lemonlink.eu" + middlewares: + - crowdsec-bouncer@file + - security-headers@file + - rate-limit@file + forwardedHeaders: + trustedIPs: + # Cloudflare IPs - Keep updated! + - "173.245.48.0/20" + - "103.21.244.0/22" + - "103.22.200.0/22" + - "103.31.4.0/22" + - "141.101.64.0/18" + - "108.162.192.0/18" + - "190.93.240.0/20" + - "188.114.96.0/20" + - "197.234.240.0/22" + - "198.41.128.0/17" + - "162.158.0.0/15" + - "104.16.0.0/13" + - "104.24.0.0/14" + - "172.64.0.0/13" + - "131.0.72.0/22" + # IPv6 + - "2400:cb00::/32" + - "2606:4700::/32" + - "2803:f800::/32" + - "2405:b500::/32" + - "2405:8100::/32" + - "2a06:98c0::/29" + - "2c0f:f248::/32" + proxyProtocol: + trustedIPs: + - "173.245.48.0/20" + - "103.21.244.0/22" + - "103.22.200.0/22" + - "103.31.4.0/22" + - "141.101.64.0/18" + - "108.162.192.0/18" + - "190.93.240.0/20" + - "188.114.96.0/20" + - "197.234.240.0/22" + - "198.41.128.0/17" + - "162.158.0.0/15" + - "104.16.0.0/13" + - "104.24.0.0/14" + - "172.64.0.0/13" + - "131.0.72.0/22" + + # Internal EntryPoint (Tailscale/VPN only) + internal: + address: ":8443" # Use non-standard port, map via Tailscale + http: + tls: + certResolver: letsencrypt + domains: + - main: "local.lemonlink.eu" + sans: + - "*.local.lemonlink.eu" + middlewares: + - security-headers@file + - rate-limit-internal@file + + # Health check + ping: + address: ":8082" + +providers: + docker: + exposedByDefault: false + network: services + watch: true + file: + directory: /dynamic + watch: true + +certificatesResolvers: + letsencrypt: + acme: + email: ${CF_API_EMAIL} + storage: /letsencrypt/acme.json + tlsChallenge: {} + # Use DNS challenge for wildcard certificates + dnsChallenge: + provider: cloudflare + delayBeforeCheck: 10 + resolvers: + - "1.1.1.1:53" + - "8.8.8.8:53" + + # Staging resolver for testing + letsencrypt-staging: + acme: + email: ${CF_API_EMAIL} + storage: /letsencrypt/acme-staging.json + caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" + tlsChallenge: {} + dnsChallenge: + provider: cloudflare + delayBeforeCheck: 10 + +serversTransport: + insecureSkipVerify: false