327 lines
8.6 KiB
Markdown
327 lines
8.6 KiB
Markdown
# NPM Logging & Audit Guide
|
|
|
|
> Centralized logging for all external access
|
|
> Created: 2025-11-11
|
|
|
|
## Why NPM is the Logging Hub
|
|
|
|
**All public service access routes through NPM**, which means:
|
|
- Every external request is logged
|
|
- Failed authentication attempts tracked
|
|
- Rate limiting violations recorded
|
|
- SSL certificate renewals logged
|
|
- Configuration changes audited
|
|
|
|
## Accessing NPM Logs
|
|
|
|
### Via NPM UI
|
|
|
|
**Access NPM admin panel:**
|
|
- VPN: http://10.99.0.1:81
|
|
- Local: http://192.168.86.149:81
|
|
|
|
**View logs:**
|
|
1. Navigate to each Proxy Host
|
|
2. Click "View Logs" button
|
|
3. See real-time access logs
|
|
4. Filter by status code, IP, user agent
|
|
|
|
### Via Docker Logs
|
|
|
|
**Real-time monitoring:**
|
|
```bash
|
|
# All NPM logs
|
|
docker logs -f nginx-proxy-manager
|
|
|
|
# Filter for access logs only
|
|
docker logs -f nginx-proxy-manager 2>&1 | grep -i "access"
|
|
|
|
# Filter for errors
|
|
docker logs -f nginx-proxy-manager 2>&1 | grep -i "error"
|
|
|
|
# Filter for specific service (e.g., Jellyfin)
|
|
docker logs -f nginx-proxy-manager 2>&1 | grep "media.schweitz.net"
|
|
```
|
|
|
|
### Via Log Files
|
|
|
|
**Log location:**
|
|
```bash
|
|
# Access logs stored in container volume
|
|
ls -lh ~/docker-data/nginx-proxy-manager/data/logs/
|
|
|
|
# View access logs
|
|
tail -f ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
|
|
|
|
# View error logs
|
|
tail -f ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*_error.log
|
|
```
|
|
|
|
## Log Format
|
|
|
|
**Standard NPM access log:**
|
|
```
|
|
192.0.2.1 - - [11/Nov/2025:21:30:15 +0100] "GET /api/users HTTP/2.0" 200 1234 "https://media.schweitz.net" "Mozilla/5.0..."
|
|
```
|
|
|
|
**Fields:**
|
|
- **IP address**: Client IP (or Cloudflare IP if proxied)
|
|
- **Timestamp**: When request occurred
|
|
- **HTTP method**: GET, POST, etc.
|
|
- **Request path**: /api/users
|
|
- **Protocol**: HTTP/2.0
|
|
- **Status code**: 200 (success), 404 (not found), 403 (forbidden), etc.
|
|
- **Bytes sent**: Response size
|
|
- **Referer**: Previous page
|
|
- **User agent**: Browser/client info
|
|
|
|
## Useful Log Queries
|
|
|
|
### Find Failed Login Attempts
|
|
```bash
|
|
# Status codes 401 (unauthorized) or 403 (forbidden)
|
|
grep -E " (401|403) " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
|
|
|
|
# With IP addresses
|
|
grep -E " (401|403) " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | awk '{print $1}' | sort | uniq -c | sort -nr
|
|
```
|
|
|
|
### Monitor Specific Service Access
|
|
```bash
|
|
# Jellyfin access
|
|
grep "media.schweitz.net" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | tail -20
|
|
|
|
# Nextcloud uploads (POST requests)
|
|
grep "cloud.schweitz.net" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | grep "POST"
|
|
```
|
|
|
|
### Identify High-Traffic IPs
|
|
```bash
|
|
# Top 10 IP addresses by request count
|
|
awk '{print $1}' ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | sort | uniq -c | sort -nr | head -10
|
|
```
|
|
|
|
### Monitor SSL Certificate Activity
|
|
```bash
|
|
# Certificate renewal attempts
|
|
docker logs nginx-proxy-manager 2>&1 | grep -i "letsencrypt"
|
|
|
|
# Certificate errors
|
|
docker logs nginx-proxy-manager 2>&1 | grep -i "certificate" | grep -i "error"
|
|
```
|
|
|
|
### Track API Usage
|
|
```bash
|
|
# API endpoint access
|
|
grep "/api/" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
|
|
|
|
# Specific API endpoint
|
|
grep "/api/login" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
|
|
```
|
|
|
|
## Security Monitoring
|
|
|
|
### Suspicious Activity Patterns
|
|
|
|
**Brute force attempts:**
|
|
```bash
|
|
# Multiple 401s from same IP (potential brute force)
|
|
grep " 401 " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
|
|
awk '{print $1}' | sort | uniq -c | sort -nr | \
|
|
awk '$1 > 10 {print "Potential brute force from " $2 " (" $1 " attempts)"}'
|
|
```
|
|
|
|
**Directory scanning:**
|
|
```bash
|
|
# Looking for 404s (scanning for vulnerabilities)
|
|
grep " 404 " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
|
|
grep -E "(wp-admin|phpmyadmin|admin|login\.php)"
|
|
```
|
|
|
|
**Unusual user agents:**
|
|
```bash
|
|
# Non-browser requests (potential bots/scrapers)
|
|
grep -v "Mozilla" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
|
|
grep -v "curl" | tail -20
|
|
```
|
|
|
|
## Log Rotation
|
|
|
|
**Automatic rotation configuration:**
|
|
|
|
NPM handles basic rotation, but for long-term storage:
|
|
|
|
```bash
|
|
# Create logrotate config
|
|
sudo tee /etc/logrotate.d/nginx-proxy-manager <<EOF
|
|
/home/jpmschweitzer/docker-data/nginx-proxy-manager/data/logs/*.log {
|
|
daily
|
|
rotate 30
|
|
compress
|
|
delaycompress
|
|
notifempty
|
|
missingok
|
|
create 0644 root root
|
|
postrotate
|
|
docker exec nginx-proxy-manager nginx -s reload > /dev/null 2>&1 || true
|
|
endscript
|
|
}
|
|
EOF
|
|
|
|
# Test logrotate config
|
|
sudo logrotate -d /etc/logrotate.d/nginx-proxy-manager
|
|
```
|
|
|
|
**Manual log cleanup:**
|
|
```bash
|
|
# Archive old logs
|
|
cd ~/docker-data/nginx-proxy-manager/data/logs/
|
|
tar -czf logs-archive-$(date +%Y%m%d).tar.gz *.log
|
|
mv logs-archive-*.tar.gz ~/backups/
|
|
|
|
# Clean logs older than 30 days
|
|
find ~/docker-data/nginx-proxy-manager/data/logs/ -name "*.log" -mtime +30 -delete
|
|
```
|
|
|
|
## Centralized Logging (Future Enhancement)
|
|
|
|
**Option 1: Ship logs to external service**
|
|
|
|
Use a log aggregator like:
|
|
- Loki + Grafana (self-hosted)
|
|
- Elasticsearch + Kibana
|
|
- Splunk
|
|
- Cloud services (Datadog, Loggly)
|
|
|
|
**Option 2: Syslog forwarding**
|
|
|
|
Configure NPM to forward to syslog:
|
|
```nginx
|
|
# Add to NPM custom nginx config
|
|
access_log syslog:server=10.99.0.1:514,tag=nginx combined;
|
|
```
|
|
|
|
**Option 3: Promtail + Loki (Recommended)**
|
|
|
|
Deploy Promtail container to tail NPM logs and send to Loki:
|
|
```yaml
|
|
# Future: stacks/promtail.yml
|
|
services:
|
|
promtail:
|
|
image: grafana/promtail:latest
|
|
volumes:
|
|
- /home/jpmschweitzer/docker-data/nginx-proxy-manager/data/logs:/var/log/nginx
|
|
- ./promtail-config.yml:/etc/promtail/config.yml
|
|
command: -config.file=/etc/promtail/config.yml
|
|
```
|
|
|
|
## Compliance Logging
|
|
|
|
**For audit trails, log these events:**
|
|
|
|
### Access Events
|
|
- ✅ All successful logins (200 responses to /login endpoints)
|
|
- ✅ Failed login attempts (401/403 responses)
|
|
- ✅ File downloads (GET requests with large response sizes)
|
|
- ✅ File uploads (POST/PUT requests)
|
|
- ✅ API calls (requests to /api/ paths)
|
|
|
|
### Security Events
|
|
- ✅ SSL certificate renewals
|
|
- ✅ Configuration changes in NPM
|
|
- ✅ Rate limit violations
|
|
- ✅ Blocked IPs (403 responses)
|
|
|
|
### Monitoring Events
|
|
- ✅ Service downtime (502/503 responses)
|
|
- ✅ Slow responses (response time tracking)
|
|
- ✅ High traffic patterns
|
|
|
|
## Alerting Setup
|
|
|
|
**Create alerts for critical events:**
|
|
|
|
### Using Uptime Kuma
|
|
|
|
Configure HTTP(s) monitors in Uptime Kuma:
|
|
- Monitor each public service
|
|
- Alert on downtime
|
|
- Track response times
|
|
|
|
### Using Custom Scripts
|
|
|
|
**Example: Alert on failed logins:**
|
|
```bash
|
|
#!/bin/bash
|
|
# /home/jpmschweitzer/scripts/monitor-failed-logins.sh
|
|
|
|
THRESHOLD=10
|
|
LOG_FILE="/home/jpmschweitzer/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log"
|
|
|
|
# Count 401s in last 5 minutes
|
|
RECENT_FAILURES=$(find ~/docker-data/nginx-proxy-manager/data/logs/ -name "proxy-host-*.log" -mmin -5 -exec grep -c " 401 " {} + | awk '{s+=$1} END {print s}')
|
|
|
|
if [ "$RECENT_FAILURES" -gt "$THRESHOLD" ]; then
|
|
echo "ALERT: $RECENT_FAILURES failed login attempts in last 5 minutes" | \
|
|
mail -s "Security Alert: High Failed Login Rate" admin@schweitz.net
|
|
fi
|
|
```
|
|
|
|
**Run via cron:**
|
|
```cron
|
|
*/5 * * * * /home/jpmschweitzer/scripts/monitor-failed-logins.sh
|
|
```
|
|
|
|
## Performance Monitoring
|
|
|
|
**Track service performance via logs:**
|
|
|
|
### Response Time Analysis
|
|
```bash
|
|
# Extract response times (if configured in NPM)
|
|
grep "upstream_response_time" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
|
|
awk '{print $NF}' | sort -n | tail -20
|
|
```
|
|
|
|
### Bandwidth Usage
|
|
```bash
|
|
# Sum bytes sent per service
|
|
awk '{sum+=$10} END {print "Total bytes: " sum " (" sum/1024/1024 " MB)"}' \
|
|
~/docker-data/nginx-proxy-manager/data/logs/proxy-host-media*.log
|
|
```
|
|
|
|
### Most Accessed Endpoints
|
|
```bash
|
|
# Top 10 requested paths
|
|
awk '{print $7}' ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
|
|
sort | uniq -c | sort -nr | head -10
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Regular Log Review
|
|
- ✅ Check NPM logs weekly for suspicious activity
|
|
- ✅ Review SSL certificate status monthly
|
|
- ✅ Archive logs older than 30 days
|
|
- ✅ Monitor for unusual traffic patterns
|
|
|
|
### Retention Policy
|
|
- **Active logs**: 30 days (on SSD)
|
|
- **Compressed archives**: 1 year (on HDD /mnt/media/backups/logs/)
|
|
- **Long-term storage**: Ship to external service if needed
|
|
|
|
### Privacy Considerations
|
|
- ⚠️ Logs contain IP addresses (PII in EU)
|
|
- ⚠️ Don't log full request bodies (may contain passwords)
|
|
- ⚠️ Rotate/delete old logs per privacy policy
|
|
- ⚠️ Secure log access (only admins via VPN)
|
|
|
|
---
|
|
|
|
**Summary:**
|
|
- All external access logged in NPM
|
|
- Logs accessible via UI, Docker, or files
|
|
- Use for security monitoring and audit trails
|
|
- Set up alerts for critical events
|
|
- Regular log review and rotation
|