12 KiB
12 KiB
| project | type | status | tags | created | updated | path | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| pbs-security-hardening | project-plan | active |
|
2026-04-02 | 2026-04-02 | 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:
- Crowdsec — Perimeter security with intelligent alerting, replacing manual Wordfence email monitoring
- 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 metricsfor activity - Check
cscli alerts listfor 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
webadminspace via Notify Travis sub-workflow - Tier 4 alerts → Google Chat
webadminspace 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 metricsfor 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
# 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
# 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
# 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
# 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