feat: add IP address logging to webhooks for whitelisting

Implements comprehensive IP logging for webhook requests to enable
IP whitelisting and security monitoring.

Features:
- Capture source IP from webhook requests (x-forwarded-for, x-real-ip)
- Capture user agent for identification
- Store in webhook_logs table
- New API endpoint: GET /api/webhooks/ips
- View unique IPs with request counts and statistics
- Identify Autotask IPs for whitelisting

Database Changes:
- Added source_ip column (VARCHAR 45) to webhook_logs
- Added user_agent column (TEXT) to webhook_logs
- Added index on source_ip for efficient queries
- Migration 005 for existing installations

API Endpoints:
- GET /api/webhooks/ips?hours=168&entityType=Tickets
  Returns unique IPs with:
  * Request counts (total, successful, failed)
  * First/last seen timestamps
  * Entity types accessed
  * User agent strings

Use Cases:
1. Identify Autotask webhook IPs
2. Configure IP whitelist in nginx/Pangolin/Cloudflare
3. Monitor for unauthorized webhook attempts
4. Audit webhook sources
5. Detect IP changes from Autotask

Security Benefits:
- Enable IP whitelisting for webhook endpoint
- Block unauthorized webhook attempts
- Monitor for suspicious activity
- Audit trail of webhook sources

Documentation:
- Complete IP whitelisting guide (WEBHOOK_IP_WHITELISTING.md)
- Configuration examples for nginx, Pangolin, Cloudflare
- Monitoring queries and best practices
- Troubleshooting guide

Files Modified:
- migrations/004_webhook_support.sql - Added IP columns
- migrations/005_add_webhook_ip_logging.sql - Migration for existing installs
- lib/types/webhook.ts - Added IP fields to WebhookLog
- lib/services/webhook-service.ts - Capture and log IPs
- app/api/webhooks/autotask/route.ts - Extract IP from headers
- app/api/webhooks/ips/route.ts - New IP viewing endpoint
- docs/WEBHOOK_IP_WHITELISTING.md - Complete guide

Next Steps:
1. Run migration (004 for new, 005 for existing)
2. Deploy updated code
3. Receive webhooks from Autotask
4. View IPs via /api/webhooks/ips
5. Configure IP whitelist in proxy/tunnel
This commit is contained in:
root 2026-01-24 17:28:56 -05:00
parent 8e83347138
commit a093f8787c
7 changed files with 672 additions and 7 deletions

View file

@ -0,0 +1,540 @@
# Webhook IP Whitelisting Guide
## Overview
This guide explains how to identify Autotask's webhook source IPs and configure IP whitelisting to secure your webhook endpoint.
---
## Why IP Whitelisting?
**Benefits:**
- ✅ Only allow webhooks from Autotask's servers
- ✅ Block unauthorized webhook attempts
- ✅ Additional security layer beyond HTTPS
- ✅ Prevent abuse and DDoS attacks
**When to Use:**
- Production environments
- High-security requirements
- After identifying Autotask's IP ranges
---
## Step 1: Identify Autotask IPs
### **Method 1: View Webhook Logs (Recommended)**
After receiving a few webhooks from Autotask, check the logged IPs:
**Via API:**
```bash
curl https://your-domain.com/api/webhooks/ips?hours=168
```
Response:
```json
{
"success": true,
"ips": [
{
"ip": "52.1.2.3",
"userAgent": "Autotask-Webhook/1.0",
"requestCount": 150,
"successfulCount": 148,
"failedCount": 2,
"firstSeen": "2026-01-20T10:00:00Z",
"lastSeen": "2026-01-24T15:00:00Z",
"entityTypes": ["Tickets", "Companies", "Tasks"]
},
{
"ip": "52.5.6.7",
"userAgent": "Autotask-Webhook/1.0",
"requestCount": 75,
"successfulCount": 75,
"failedCount": 0,
"firstSeen": "2026-01-22T08:00:00Z",
"lastSeen": "2026-01-24T14:30:00Z",
"entityTypes": ["TimeEntries", "Projects"]
}
],
"count": 2,
"period": "168 hours"
}
```
**Via Database:**
```sql
-- Get unique IPs with request counts
SELECT
source_ip,
user_agent,
COUNT(*) as request_count,
MIN(received_at) as first_seen,
MAX(received_at) as last_seen,
COUNT(*) FILTER (WHERE status = 'processed') as successful,
COUNT(*) FILTER (WHERE status = 'failed') as failed
FROM webhook_logs
WHERE received_at >= NOW() - INTERVAL '7 days'
AND source_ip IS NOT NULL
GROUP BY source_ip, user_agent
ORDER BY request_count DESC;
```
**Identify Autotask IPs:**
- Look for IPs with `Autotask-Webhook` user agent
- High request counts (if you have active webhooks)
- Multiple entity types
- Consistent activity pattern
### **Method 2: Contact Autotask Support**
Request official IP ranges from Autotask:
- Open support ticket
- Ask for "Webhook source IP ranges"
- They may provide CIDR blocks (e.g., `52.1.0.0/16`)
### **Method 3: Check Autotask Documentation**
Some Autotask zones publish their IP ranges:
- Check Autotask developer documentation
- Look for "Webhook IP ranges" or "API IP addresses"
- May vary by zone (ww1, ww5, ww15, etc.)
---
## Step 2: Configure IP Whitelist
### **Option A: Nginx**
Edit your nginx configuration:
```nginx
server {
listen 443 ssl http2;
server_name webhooks.yourdomain.com;
# ... SSL configuration ...
location = /api/webhooks/autotask {
# IP Whitelist - Autotask webhook IPs
allow 52.1.2.3; # Autotask IP 1
allow 52.5.6.7; # Autotask IP 2
allow 52.10.0.0/16; # Autotask IP range (CIDR)
deny all; # Block everything else
# Rate limiting
limit_req zone=webhook_limit burst=20 nodelay;
# Proxy to Pulse app
proxy_pass http://pulse_app;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
**Test and reload:**
```bash
sudo nginx -t
sudo systemctl reload nginx
```
### **Option B: Pangolin**
Update your Pangolin configuration:
```yaml
# /etc/pangolin/config.yml
name: pulse-webhooks
ingress:
- hostname: webhooks.yourdomain.com
path: /api/webhooks/autotask
service: http://localhost:3100
security:
# IP Whitelist
ipWhitelist:
- "52.1.2.3" # Autotask IP 1
- "52.5.6.7" # Autotask IP 2
- "52.10.0.0/16" # Autotask IP range
# Rate limiting
rateLimit:
enabled: true
requestsPerMinute: 100
burstSize: 20
```
**Restart tunnel:**
```bash
sudo systemctl restart pangolin-tunnel
```
### **Option C: Cloudflare Tunnel**
Use Cloudflare Access rules:
1. **Go to Cloudflare Dashboard** → Zero Trust → Access → Applications
2. **Create Application:**
- Name: `Pulse Webhooks`
- Domain: `webhooks.yourdomain.com`
- Path: `/api/webhooks/autotask`
3. **Add Policy:**
- Name: `Autotask IPs Only`
- Action: `Allow`
- Rule: `IP ranges`
- Values: `52.1.2.3`, `52.5.6.7`, `52.10.0.0/16`
4. **Save**
### **Option D: Firewall (UFW)**
If using UFW on the server:
```bash
# Allow specific IPs to port 443
sudo ufw allow from 52.1.2.3 to any port 443
sudo ufw allow from 52.5.6.7 to any port 443
sudo ufw allow from 52.10.0.0/16 to any port 443
# Deny all other traffic to port 443 (if not already blocked)
# Be careful with this - ensure you have other access methods!
# sudo ufw deny 443
# Check rules
sudo ufw status numbered
```
**Warning:** Be careful with firewall rules. Ensure you don't lock yourself out!
---
## Step 3: Test IP Whitelist
### **1. Test from Allowed IP (Autotask)**
Trigger a webhook from Autotask:
- Create a test ticket or company
- Check webhook logs to verify it was received
```bash
# Check recent webhooks
curl https://your-domain.com/api/webhooks/logs?limit=5
```
### **2. Test from Blocked IP**
From a different IP (your computer):
```bash
curl -X POST https://webhooks.yourdomain.com/api/webhooks/autotask \
-H "Content-Type: application/json" \
-d '{"eventId":"test","eventType":"create","entityType":"Tickets","entityId":1}'
```
**Expected result:**
- **Nginx:** `403 Forbidden`
- **Pangolin/Cloudflare:** Connection refused or 403
- **Firewall:** Connection timeout
### **3. Verify Logs**
Check that only Autotask IPs are getting through:
```sql
-- Recent webhook IPs
SELECT source_ip, COUNT(*) as count
FROM webhook_logs
WHERE received_at >= NOW() - INTERVAL '1 hour'
GROUP BY source_ip;
```
Should only show Autotask IPs.
---
## Monitoring IP Changes
### **Set Up Alerts for New IPs**
Create a monitoring query to detect new source IPs:
```sql
-- Find IPs seen in last 24 hours that weren't seen before
SELECT DISTINCT wl.source_ip, wl.user_agent, MIN(wl.received_at) as first_seen
FROM webhook_logs wl
WHERE wl.received_at >= NOW() - INTERVAL '24 hours'
AND wl.source_ip NOT IN (
SELECT DISTINCT source_ip
FROM webhook_logs
WHERE received_at < NOW() - INTERVAL '24 hours'
AND received_at >= NOW() - INTERVAL '30 days'
)
GROUP BY wl.source_ip, wl.user_agent;
```
### **Regular IP Audit**
Run weekly to check for IP changes:
```bash
# Get IPs from last 7 days
curl https://your-domain.com/api/webhooks/ips?hours=168 > webhook-ips-$(date +%Y%m%d).json
# Compare with previous week
diff webhook-ips-20260117.json webhook-ips-20260124.json
```
If new IPs appear:
1. Verify they're legitimate Autotask IPs
2. Check user agent matches `Autotask-Webhook`
3. Update whitelist configuration
4. Reload/restart proxy or tunnel
---
## Troubleshooting
### **Webhooks Stopped Working After Whitelisting**
**Check 1: Verify Autotask IP**
```sql
-- Check recent failed webhooks
SELECT source_ip, user_agent, error_message, received_at
FROM webhook_logs
WHERE status = 'failed'
AND received_at >= NOW() - INTERVAL '1 hour'
ORDER BY received_at DESC;
```
If you see legitimate Autotask IPs being blocked, add them to whitelist.
**Check 2: Proxy Logs**
**Nginx:**
```bash
tail -f /var/log/nginx/pulse-webhooks-error.log | grep 403
```
**Pangolin:**
```bash
sudo journalctl -u pangolin-tunnel -f | grep denied
```
**Check 3: Test Without Whitelist**
Temporarily disable IP whitelist to confirm that's the issue:
**Nginx:** Comment out `allow`/`deny` lines and reload
**Pangolin:** Remove `ipWhitelist` section and restart
If webhooks work without whitelist, the issue is IP configuration.
### **Autotask Changed IPs**
Autotask may change their webhook source IPs:
1. **Check webhook logs** for new IPs
2. **Verify** they're legitimate (user agent, request pattern)
3. **Update whitelist** with new IPs
4. **Keep old IPs** for a few days (in case of rollback)
5. **Remove old IPs** after confirming they're no longer used
### **Getting Blocked Yourself**
If you accidentally block legitimate traffic:
**Quick Fix (Nginx):**
```bash
# Edit config to remove IP whitelist
sudo nano /etc/nginx/sites-available/pulse-webhooks
# Comment out the deny rules
# deny all;
# Reload
sudo systemctl reload nginx
```
**Quick Fix (Pangolin):**
```bash
# Edit config
sudo nano /etc/pangolin/config.yml
# Remove or comment ipWhitelist section
# Restart
sudo systemctl restart pangolin-tunnel
```
---
## Best Practices
### **1. Use CIDR Blocks When Possible**
Instead of individual IPs:
```nginx
# Better
allow 52.10.0.0/16;
# Less flexible
allow 52.10.1.1;
allow 52.10.1.2;
allow 52.10.1.3;
```
### **2. Document Your Whitelist**
Keep a record of:
- Which IPs are whitelisted
- When they were added
- Source of information (Autotask support, logs, etc.)
- Last verified date
Example:
```yaml
# IP Whitelist Documentation
# Last updated: 2026-01-24
ipWhitelist:
- "52.1.2.3" # Added: 2026-01-20, Source: Webhook logs
- "52.5.6.7" # Added: 2026-01-22, Source: Webhook logs
- "52.10.0.0/16" # Added: 2026-01-24, Source: Autotask support ticket #12345
```
### **3. Monitor Regularly**
- Weekly: Check for new IPs in logs
- Monthly: Audit whitelist against active IPs
- After Autotask updates: Verify webhooks still work
### **4. Combine with Other Security**
IP whitelisting is one layer. Also use:
- ✅ HTTPS/TLS encryption
- ✅ Rate limiting
- ✅ Path-based access control
- ✅ Request validation
- ✅ Monitoring and alerting
### **5. Have a Rollback Plan**
Keep configuration backups:
```bash
# Backup before changes
sudo cp /etc/nginx/sites-available/pulse-webhooks \
/etc/nginx/sites-available/pulse-webhooks.backup-$(date +%Y%m%d)
# Or for Pangolin
sudo cp /etc/pangolin/config.yml \
/etc/pangolin/config.yml.backup-$(date +%Y%m%d)
```
---
## API Reference
### **GET /api/webhooks/ips**
Get unique source IPs from webhook logs.
**Parameters:**
- `hours` (optional): Number of hours to look back (default: 168 = 7 days)
- `entityType` (optional): Filter by entity type
**Example:**
```bash
# Last 7 days
curl https://your-domain.com/api/webhooks/ips?hours=168
# Last 24 hours, tickets only
curl https://your-domain.com/api/webhooks/ips?hours=24&entityType=Tickets
```
**Response:**
```json
{
"success": true,
"ips": [
{
"ip": "52.1.2.3",
"userAgent": "Autotask-Webhook/1.0",
"requestCount": 150,
"successfulCount": 148,
"failedCount": 2,
"firstSeen": "2026-01-20T10:00:00Z",
"lastSeen": "2026-01-24T15:00:00Z",
"entityTypes": ["Tickets", "Companies"]
}
],
"count": 1,
"period": "168 hours"
}
```
---
## Database Queries
### **Get All Unique IPs**
```sql
SELECT DISTINCT source_ip, user_agent
FROM webhook_logs
WHERE source_ip IS NOT NULL
ORDER BY source_ip;
```
### **Get IP Activity Summary**
```sql
SELECT
source_ip,
COUNT(*) as total_requests,
COUNT(DISTINCT entity_type) as entity_types_count,
MIN(received_at) as first_seen,
MAX(received_at) as last_seen,
ROUND(AVG(processing_time_ms), 2) as avg_processing_ms
FROM webhook_logs
WHERE source_ip IS NOT NULL
GROUP BY source_ip
ORDER BY total_requests DESC;
```
### **Detect Suspicious IPs**
```sql
-- IPs with high failure rates
SELECT
source_ip,
user_agent,
COUNT(*) as total,
COUNT(*) FILTER (WHERE status = 'failed') as failed,
ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'failed') / COUNT(*), 2) as failure_rate
FROM webhook_logs
WHERE source_ip IS NOT NULL
AND received_at >= NOW() - INTERVAL '24 hours'
GROUP BY source_ip, user_agent
HAVING COUNT(*) FILTER (WHERE status = 'failed') > 5
ORDER BY failure_rate DESC;
```
---
## Summary
**Steps to Enable IP Whitelisting:**
1. ✅ **Run migration** to add IP logging columns
2. ✅ **Deploy updated code** with IP capture
3. ✅ **Receive webhooks** from Autotask (let it run for a day)
4. ✅ **Identify Autotask IPs** via `/api/webhooks/ips` endpoint
5. ✅ **Configure whitelist** in your proxy/tunnel
6. ✅ **Test** that webhooks still work
7. ✅ **Monitor** for new IPs regularly
**Result:**
- Only Autotask's IPs can send webhooks
- Unauthorized requests are blocked
- Additional security layer for your webhook endpoint
- Logged IP data for audit and troubleshooting