Create pbs-security-hardening.md via n8n
This commit is contained in:
parent
1b31cb574e
commit
c44d2428d0
410
PBS/Tech/Projects/pbs-security-hardening.md
Normal file
410
PBS/Tech/Projects/pbs-security-hardening.md
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
---
|
||||||
|
project: pbs-security-hardening
|
||||||
|
type: project-plan
|
||||||
|
status: active
|
||||||
|
tags:
|
||||||
|
- pbs
|
||||||
|
- docker
|
||||||
|
- traefik
|
||||||
|
- security
|
||||||
|
- crowdsec
|
||||||
|
- authelia
|
||||||
|
- cloudflare
|
||||||
|
- staging
|
||||||
|
- production
|
||||||
|
- automation
|
||||||
|
- n8n
|
||||||
|
created: 2026-04-02
|
||||||
|
updated: 2026-04-02
|
||||||
|
path: PBS/Tech/Projects
|
||||||
|
---
|
||||||
|
|
||||||
|
# PBS Security Hardening - Unified Project Plan
|
||||||
|
|
||||||
|
## Project Goal
|
||||||
|
|
||||||
|
Deploy a layered security stack for PBS infrastructure across staging and
|
||||||
|
production on Linode. Two components:
|
||||||
|
|
||||||
|
1. **Crowdsec** — Perimeter security with intelligent alerting, replacing
|
||||||
|
manual Wordfence email monitoring
|
||||||
|
2. **Authelia** — SSO with 2FA for all admin/infrastructure services
|
||||||
|
|
||||||
|
**Approach:** Deploy and validate on staging first, then roll to production.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Security Baseline
|
||||||
|
|
||||||
|
### What's in place today
|
||||||
|
- Cloudflare: Edge WAF, DNS, CDN, Bot Fight Mode
|
||||||
|
- Wordfence: WordPress-level firewall, login lockout (5 attempts), malware
|
||||||
|
scanning
|
||||||
|
- 2FA: Enabled on WordPress admin accounts
|
||||||
|
- fail2ban: Server-level SSH protection
|
||||||
|
- UFW: Firewall with Docker-aware rules in after.rules
|
||||||
|
- Cloudflare proxy disabled for n8n and Gitea subdomains
|
||||||
|
|
||||||
|
### Problems being solved
|
||||||
|
- ~20 lockout notification emails per day (noise)
|
||||||
|
- No automated intelligent blocking — manual review required
|
||||||
|
- Targeted login attempts using real admin usernames (concerning)
|
||||||
|
- No SSO — separate credentials for Portainer, Uptime Kuma, n8n, pbs-hub,
|
||||||
|
phpMyAdmin
|
||||||
|
- No confidence that attacks aren't slipping through
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Traffic flow after implementation
|
||||||
|
```
|
||||||
|
Internet → Cloudflare (edge WAF/CDN)
|
||||||
|
→ Traefik
|
||||||
|
→ Crowdsec Bouncer (block/allow decision)
|
||||||
|
→ Authelia (SSO check for admin services)
|
||||||
|
→ Service (Portainer, n8n, pbs-hub, etc.)
|
||||||
|
→ WordPress (own auth, Wordfence for malware scanning only)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tiered alerting model (Crowdsec)
|
||||||
|
- **Tier 1 — Silent block:** Generic bot attempts (admin, test, wp-admin).
|
||||||
|
Automatic. No notification.
|
||||||
|
- **Tier 2 — Logged, weekly review:** Coordinated bot patterns, repeated
|
||||||
|
IPs. Crowdsec handles it.
|
||||||
|
- **Tier 3 — Immediate alert:** Real admin username targeted. Requires
|
||||||
|
WordPress auth log as additional Crowdsec data source. Notification via n8n
|
||||||
|
→ Google Chat.
|
||||||
|
- **Tier 4 — Critical alert:** Successful login from unknown IP, failed 2FA
|
||||||
|
on real account, file integrity changes.
|
||||||
|
|
||||||
|
### Services protected by Authelia
|
||||||
|
- Portainer
|
||||||
|
- Uptime Kuma
|
||||||
|
- n8n
|
||||||
|
- pbs-hub
|
||||||
|
- phpMyAdmin
|
||||||
|
- Traefik dashboard
|
||||||
|
|
||||||
|
### Services NOT behind Authelia
|
||||||
|
- WordPress (uses its own auth + Wordfence + 2FA)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Crowdsec on Staging
|
||||||
|
|
||||||
|
### Estimated time: 3-4 hours
|
||||||
|
|
||||||
|
### Step 1.1: Deploy Crowdsec container
|
||||||
|
- [ ] Add Crowdsec service to staging docker-compose.yml
|
||||||
|
- [ ] Configure to read Traefik access logs
|
||||||
|
- [ ] Install WordPress collection (crowdsecurity/wordpress)
|
||||||
|
- [ ] Install Traefik collection (crowdsecurity/traefik)
|
||||||
|
- [ ] Verify container starts and parses logs
|
||||||
|
|
||||||
|
### Step 1.2: Install Traefik Bouncer
|
||||||
|
- [ ] Add Crowdsec Traefik bouncer plugin to Traefik config
|
||||||
|
- [ ] Configure bouncer API key
|
||||||
|
- [ ] Verify bouncer communicates with Crowdsec
|
||||||
|
- [ ] Test: manually ban an IP via `cscli`, confirm it gets blocked at
|
||||||
|
Traefik
|
||||||
|
|
||||||
|
### Step 1.3: Add WordPress auth log source
|
||||||
|
- [ ] Add WPCode snippet or lightweight plugin to log failed logins with
|
||||||
|
usernames to a file
|
||||||
|
- [ ] Mount log file into Crowdsec container
|
||||||
|
- [ ] Configure Crowdsec to parse WordPress auth log as additional
|
||||||
|
datasource
|
||||||
|
- [ ] Verify Crowdsec sees username-level login data
|
||||||
|
|
||||||
|
### Step 1.4: Configure community blocklists
|
||||||
|
- [ ] Register free Crowdsec console account
|
||||||
|
- [ ] Enroll staging instance
|
||||||
|
- [ ] Enable community blocklists
|
||||||
|
- [ ] Verify blocked IPs appear from community intelligence
|
||||||
|
|
||||||
|
### Step 1.5: Validate on staging
|
||||||
|
- [ ] Simulate brute force from test IP — confirm auto-block
|
||||||
|
- [ ] Simulate real username attempt — confirm Tier 3 detection
|
||||||
|
- [ ] Verify WordPress still accessible for normal logins
|
||||||
|
- [ ] Verify admin tools still accessible
|
||||||
|
- [ ] Check `cscli metrics` for activity
|
||||||
|
- [ ] Check `cscli alerts list` for decisions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Crowdsec Alerting via n8n
|
||||||
|
|
||||||
|
### Estimated time: 2-3 hours
|
||||||
|
|
||||||
|
### Step 2.1: Crowdsec notification setup
|
||||||
|
- [ ] Configure Crowdsec HTTP notification plugin
|
||||||
|
- [ ] Point notifications at n8n webhook endpoint
|
||||||
|
- [ ] Define alert profiles matching tiered model:
|
||||||
|
- Tier 1: No notification (silent block)
|
||||||
|
- Tier 2: Logged only (weekly digest — future enhancement)
|
||||||
|
- Tier 3+: Immediate webhook to n8n
|
||||||
|
|
||||||
|
### Step 2.2: n8n alerting workflow
|
||||||
|
- [ ] Create n8n workflow: Crowdsec webhook → parse alert → route by
|
||||||
|
severity
|
||||||
|
- [ ] Tier 3 alerts → Google Chat `webadmin` space via Notify Travis
|
||||||
|
sub-workflow
|
||||||
|
- [ ] Tier 4 alerts → Google Chat `webadmin` space with urgent flag
|
||||||
|
- [ ] Include: IP, reason, username (if available), timestamp, action taken
|
||||||
|
- [ ] Test end-to-end: trigger alert → verify Google Chat notification
|
||||||
|
|
||||||
|
### Step 2.3: Validate alerting
|
||||||
|
- [ ] Confirm Tier 1 produces no notification
|
||||||
|
- [ ] Confirm Tier 3 sends Google Chat alert with username info
|
||||||
|
- [ ] Confirm Tier 4 sends urgent alert
|
||||||
|
- [ ] Verify no false positives from normal browsing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Authelia on Staging
|
||||||
|
|
||||||
|
### Estimated time: 3-4 hours
|
||||||
|
|
||||||
|
### Step 3.1: Directory and secrets setup
|
||||||
|
- [ ] Create directory structure on staging: `/opt/docker/authelia/config/`
|
||||||
|
- [ ] Generate secrets: JWT, session, storage encryption, OIDC HMAC
|
||||||
|
- [ ] Generate password hashes for Travis and Jenny (argon2id)
|
||||||
|
- [ ] Create users_database.yml with both accounts
|
||||||
|
|
||||||
|
### Step 3.2: Authelia configuration
|
||||||
|
- [ ] Write configuration.yml:
|
||||||
|
- Session domain: staging domain
|
||||||
|
- Default policy: two_factor
|
||||||
|
- Storage: SQLite (local file)
|
||||||
|
- Notifier: filesystem (for staging — SMTP for production later)
|
||||||
|
- TOTP settings: SHA1, 6 digits, 30 second period
|
||||||
|
- [ ] Define access control rules:
|
||||||
|
- Portainer, Uptime Kuma, n8n, pbs-hub, phpMyAdmin, Traefik dashboard →
|
||||||
|
two_factor
|
||||||
|
- WordPress and public routes → bypass
|
||||||
|
|
||||||
|
### Step 3.3: Docker and Traefik integration
|
||||||
|
- [ ] Create Authelia docker-compose.yml
|
||||||
|
- [ ] Add Authelia to Traefik network
|
||||||
|
- [ ] Configure Traefik forwardAuth middleware pointing to Authelia
|
||||||
|
- [ ] Add middleware labels to all protected services
|
||||||
|
- [ ] Leave WordPress compose labels unchanged
|
||||||
|
|
||||||
|
### Step 3.4: Deploy and test
|
||||||
|
- [ ] Start Authelia container
|
||||||
|
- [ ] Navigate to a protected service — verify redirect to Authelia login
|
||||||
|
- [ ] Login with Travis account — verify redirect back to service
|
||||||
|
- [ ] Register TOTP device
|
||||||
|
- [ ] Verify 2FA prompt works
|
||||||
|
- [ ] Test SSO: login once, access other protected services without re-auth
|
||||||
|
- [ ] Verify WordPress login is unaffected
|
||||||
|
- [ ] Verify public site is unaffected
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Integration Testing on Staging
|
||||||
|
|
||||||
|
### Estimated time: 2 hours
|
||||||
|
|
||||||
|
### Step 4.1: Crowdsec + Authelia together
|
||||||
|
- [ ] Verify Crowdsec bouncer doesn't interfere with Authelia redirects
|
||||||
|
- [ ] Verify blocked IPs get Crowdsec block page, not Authelia login
|
||||||
|
- [ ] Test: brute force Authelia login → Crowdsec detects and blocks
|
||||||
|
- [ ] Verify request flow: Cloudflare → Traefik → Crowdsec → Authelia →
|
||||||
|
Service
|
||||||
|
|
||||||
|
### Step 4.2: Full stack validation
|
||||||
|
- [ ] All protected services accessible after Authelia login + 2FA
|
||||||
|
- [ ] WordPress admin login works independently
|
||||||
|
- [ ] Public site unaffected
|
||||||
|
- [ ] Crowdsec blocking bots silently
|
||||||
|
- [ ] Tier 3/4 alerts arriving in Google Chat
|
||||||
|
- [ ] No performance degradation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Roll to Production
|
||||||
|
|
||||||
|
### Estimated time: 2-3 hours
|
||||||
|
|
||||||
|
### Step 5.1: Pre-production prep
|
||||||
|
- [ ] Document all staging config that worked
|
||||||
|
- [ ] Generate fresh secrets for production (never reuse staging secrets)
|
||||||
|
- [ ] Update Authelia session domain to production domain
|
||||||
|
- [ ] Switch Authelia notifier from filesystem to SMTP (Google Workspace)
|
||||||
|
- [ ] Update Crowdsec webhook URL to production n8n
|
||||||
|
|
||||||
|
### Step 5.2: Deploy Crowdsec to production
|
||||||
|
- [ ] Add Crowdsec to production docker-compose.yml
|
||||||
|
- [ ] Add Traefik bouncer plugin
|
||||||
|
- [ ] Add WordPress auth log source
|
||||||
|
- [ ] Enroll in Crowdsec console
|
||||||
|
- [ ] Verify blocking and alerting work
|
||||||
|
|
||||||
|
### Step 5.3: Deploy Authelia to production
|
||||||
|
- [ ] Add Authelia to production docker-compose.yml
|
||||||
|
- [ ] Add forwardAuth middleware to all protected services
|
||||||
|
- [ ] Verify SSO and 2FA work
|
||||||
|
- [ ] Register TOTP devices for Travis and Jenny
|
||||||
|
|
||||||
|
### Step 5.4: Post-deployment
|
||||||
|
- [ ] Monitor for 48 hours — watch for false positives
|
||||||
|
- [ ] Tune Crowdsec scenarios if needed
|
||||||
|
- [ ] Verify Wordfence lockout emails have dropped significantly
|
||||||
|
- [ ] Demote Wordfence to malware-scanning-only role
|
||||||
|
- [ ] Consider disabling Wordfence firewall features (now redundant)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Codify in Ansible
|
||||||
|
|
||||||
|
### Estimated time: 3-4 hours
|
||||||
|
|
||||||
|
### Step 6.1: Crowdsec Ansible role
|
||||||
|
- [ ] Create Crowdsec tasks with Ansible tags (`--tags crowdsec`)
|
||||||
|
- [ ] Template docker-compose.yml with variables
|
||||||
|
- [ ] Template Crowdsec acquis.yaml (log sources)
|
||||||
|
- [ ] Store bouncer API key in ansible-vault
|
||||||
|
- [ ] Store console enrollment key in ansible-vault
|
||||||
|
|
||||||
|
### Step 6.2: Authelia Ansible role
|
||||||
|
- [ ] Create Authelia tasks with Ansible tags (`--tags authelia`)
|
||||||
|
- [ ] Template configuration.yml with variables
|
||||||
|
- [ ] Template users_database.yml
|
||||||
|
- [ ] Store all secrets in ansible-vault
|
||||||
|
- [ ] Add forwardAuth middleware labels to protected service templates
|
||||||
|
|
||||||
|
### Step 6.3: Validate
|
||||||
|
- [ ] Destroy staging and rebuild from Ansible
|
||||||
|
- [ ] Verify Crowdsec + Authelia come up correctly
|
||||||
|
- [ ] Verify alerting works after rebuild
|
||||||
|
- [ ] Verify SSO works after rebuild
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wordfence Transition Plan
|
||||||
|
|
||||||
|
After Crowdsec is stable on production:
|
||||||
|
|
||||||
|
- [ ] Disable Wordfence firewall features (redundant with Crowdsec)
|
||||||
|
- [ ] Keep Wordfence for: scheduled malware scans (monthly), file integrity
|
||||||
|
checks
|
||||||
|
- [ ] Disable Wordfence login protection (Crowdsec handles this now)
|
||||||
|
- [ ] Disable lockout email notifications (the whole point!)
|
||||||
|
- [ ] Document remaining Wordfence role in tech wiki
|
||||||
|
- [ ] Long-term: when WordPress is replaced, Wordfence goes away entirely
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Maintenance Cadence
|
||||||
|
|
||||||
|
After full deployment:
|
||||||
|
|
||||||
|
### Daily (glance, 1 min)
|
||||||
|
- Check Uptime Kuma — green means good
|
||||||
|
- Respond to any Tier 3/4 Google Chat alerts
|
||||||
|
|
||||||
|
### Weekly (10 min)
|
||||||
|
- Review Crowdsec console dashboard for trends
|
||||||
|
- Check `cscli metrics` for volume patterns
|
||||||
|
- Update WordPress plugins on staging, then production
|
||||||
|
|
||||||
|
### Monthly (30 min)
|
||||||
|
- Run Wordfence malware scan
|
||||||
|
- Review WordPress user accounts
|
||||||
|
- Check Docker image updates (Traefik, WordPress, Crowdsec, Authelia)
|
||||||
|
- Review Cloudflare analytics
|
||||||
|
- Spot-check Crowdsec community blocklist enrollment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Crowdsec not blocking
|
||||||
|
```bash
|
||||||
|
# Check if bouncer is connected
|
||||||
|
cscli bouncers list
|
||||||
|
|
||||||
|
# Check if decisions exist
|
||||||
|
cscli decisions list
|
||||||
|
|
||||||
|
# Check log parsing
|
||||||
|
cscli metrics
|
||||||
|
|
||||||
|
# Manually test a ban
|
||||||
|
cscli decisions add --ip 1.2.3.4 --reason "test ban" --duration 5m
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authelia not redirecting
|
||||||
|
```bash
|
||||||
|
# Check Authelia logs
|
||||||
|
docker logs authelia
|
||||||
|
|
||||||
|
# Verify Traefik middleware
|
||||||
|
docker exec traefik wget -qO- http://localhost:8080/api/http/middlewares
|
||||||
|
|
||||||
|
# Check forwardAuth is reaching Authelia
|
||||||
|
docker logs authelia | grep "authorization"
|
||||||
|
```
|
||||||
|
|
||||||
|
### False positives
|
||||||
|
```bash
|
||||||
|
# Whitelist your own IP
|
||||||
|
cscli decisions delete --ip YOUR_IP
|
||||||
|
cscli parsers install crowdsecurity/whitelists
|
||||||
|
# Add your IP to /etc/crowdsec/parsers/s02-enrich/whitelists.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Locked out of Authelia
|
||||||
|
```bash
|
||||||
|
# Reset user password
|
||||||
|
docker exec authelia authelia hash-password -- 'newpassword'
|
||||||
|
# Update users_database.yml with new hash
|
||||||
|
docker restart authelia
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
### Crowdsec
|
||||||
|
- Official docs: https://docs.crowdsec.net/
|
||||||
|
- Traefik bouncer:
|
||||||
|
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
|
- WordPress collection:
|
||||||
|
https://hub.crowdsec.net/author/crowdsecurity/collections/wordpress
|
||||||
|
- Console: https://app.crowdsec.net/
|
||||||
|
|
||||||
|
### Authelia
|
||||||
|
- Official docs: https://www.authelia.com/
|
||||||
|
- Configuration reference:
|
||||||
|
https://www.authelia.com/configuration/prologue/introduction/
|
||||||
|
- Traefik integration: https://www.authelia.com/integration/proxies/traefik/
|
||||||
|
- File-based users: https://www.authelia.com/reference/guides/passwords/
|
||||||
|
|
||||||
|
### Traefik
|
||||||
|
- ForwardAuth middleware:
|
||||||
|
https://doc.traefik.io/traefik/middlewares/http/forwardauth/
|
||||||
|
- Plugin catalog: https://plugins.traefik.io/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Wordfence lockout emails drop to near zero
|
||||||
|
- [ ] Crowdsec silently blocking bot traffic at Traefik layer
|
||||||
|
- [ ] Tier 3/4 alerts arriving in Google Chat when real threats detected
|
||||||
|
- [ ] All admin services behind Authelia SSO with 2FA
|
||||||
|
- [ ] WordPress auth unaffected
|
||||||
|
- [ ] Public site unaffected
|
||||||
|
- [ ] Everything codified in Ansible and reproducible
|
||||||
|
- [ ] Daily security overhead reduced from 15+ minutes to under 1 minute
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: April 2, 2026*
|
||||||
|
*Maintained by: Travis*
|
||||||
|
*Project: Plant Based Southerner Infrastructure*
|
||||||
|
|
||||||
|
...sent from Jenny & Travis
|
||||||
Loading…
Reference in New Issue
Block a user