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
This commit is contained in:
ImpulsiveFPS 2026-02-03 12:09:13 +01:00
commit d0a2273b71
32 changed files with 4577 additions and 0 deletions

59
.env.example Normal file
View File

@ -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=...

53
.gitignore vendored Normal file
View File

@ -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/

239
GIT-REPO-SETUP.md Normal file
View File

@ -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)

254
MIGRATE-FROM-NPM.md Normal file
View File

@ -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/

299
PORTAINER-DEPLOY.md Normal file
View File

@ -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

249
QUICKSTART.md Normal file
View File

@ -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

162
README-DEPLOY.md Normal file
View File

@ -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`

312
README.md Normal file
View File

@ -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.

244
SETUP-TRUENAS-NEXTCLOUD.md Normal file
View File

@ -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

215
SETUP.md Normal file
View File

@ -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**

110
SUMMARY.md Normal file
View File

@ -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

203
authelia/configuration.yml Normal file
View File

@ -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 <auth@lemonlink.eu>"
# 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
# ...

View File

@ -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

24
crowdsec/acquis.yaml Normal file
View File

@ -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

View File

@ -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"

View File

@ -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:

301
docker-compose.yml Normal file
View File

@ -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"

173
docs/CLOUDFLARE.md Normal file
View File

@ -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.

278
docs/SECURITY.md Normal file
View File

@ -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 <<EOF | sudo tee /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
EOF
sudo systemctl restart fail2ban
# Disable password SSH (use keys only)
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# Configure firewall (UFW)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 100.64.0.0/10 to any port 22 # Tailscale SSH only
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
### 2. Docker Security
```bash
# Install Docker rootless (optional but recommended)
dockerd-rootless-setuptool.sh install
# Or use userns-remap
echo '{"userns-remap": "default"}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1
```
### 3. Secret Generation
```bash
# Generate all secrets
cd LemonSec
# JWT Secret (32+ chars)
openssl rand -hex 32 > 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 <security@lemonlink.eu>"
```
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)

173
docs/TAILSCALE.md Normal file
View File

@ -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*

280
docs/TRUENAS-NEXTCLOUD.md Normal file
View File

@ -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

View File

@ -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"

View File

@ -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/"

View File

@ -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"

View File

@ -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

26
monitoring/prometheus.yml Normal file
View File

@ -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']

View File

@ -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

93
setup.ps1 Normal file
View File

@ -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

56
stack.env Normal file
View File

@ -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

View File

@ -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

44
traefik/dynamic/tls.yml Normal file
View File

@ -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

163
traefik/traefik.yml Normal file
View File

@ -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