docs: add reverse proxy configuration guide for webhook endpoint
Since Pulse is not publicly accessible, added comprehensive guide for exposing only the webhook endpoint to Autotask via reverse proxy. Covers 4 approaches: 1. Nginx + Let's Encrypt (recommended for production) 2. Cloudflare Tunnel (easiest, no firewall changes) 3. Caddy (automatic HTTPS) 4. Traefik (for Docker users) Each includes: - Complete configuration examples - Setup steps - Security recommendations (rate limiting, IP whitelisting) - Testing procedures - Troubleshooting guides Security features: - Only /api/webhooks/autotask exposed - SSL/TLS encryption required - Rate limiting (100 req/min, burst 20) - Optional IP whitelisting for Autotask - All other paths return 404 File: docs/WEBHOOK_REVERSE_PROXY.md
This commit is contained in:
parent
1f83456199
commit
80d8d40f06
1 changed files with 546 additions and 0 deletions
546
docs/WEBHOOK_REVERSE_PROXY.md
Normal file
546
docs/WEBHOOK_REVERSE_PROXY.md
Normal file
|
|
@ -0,0 +1,546 @@
|
||||||
|
# Webhook Reverse Proxy Configuration
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Since your Pulse application is not publicly accessible, you need to expose only the webhook endpoint to Autotask. This guide covers secure reverse proxy configurations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 1: Nginx Reverse Proxy (Recommended)
|
||||||
|
|
||||||
|
### **Architecture**
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet → Nginx (Public) → Pulse App (Internal)
|
||||||
|
Port 443 Port 3100
|
||||||
|
|
||||||
|
Only /api/webhooks/autotask is exposed
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Nginx Configuration**
|
||||||
|
|
||||||
|
Create `/etc/nginx/sites-available/pulse-webhooks`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Rate limiting zone - prevents abuse
|
||||||
|
limit_req_zone $binary_remote_addr zone=webhook_limit:10m rate=100r/m;
|
||||||
|
|
||||||
|
# Upstream to Pulse application
|
||||||
|
upstream pulse_app {
|
||||||
|
server localhost:3100; # Adjust to your Pulse app port
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name webhooks.yourdomain.com;
|
||||||
|
|
||||||
|
# Redirect HTTP to HTTPS
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name webhooks.yourdomain.com;
|
||||||
|
|
||||||
|
# SSL Configuration
|
||||||
|
ssl_certificate /etc/letsencrypt/live/webhooks.yourdomain.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/webhooks.yourdomain.com/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
access_log /var/log/nginx/pulse-webhooks-access.log;
|
||||||
|
error_log /var/log/nginx/pulse-webhooks-error.log;
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
location = /api/webhooks/autotask {
|
||||||
|
# Rate limiting - 100 requests per minute, burst of 20
|
||||||
|
limit_req zone=webhook_limit burst=20 nodelay;
|
||||||
|
|
||||||
|
# Only allow POST and GET
|
||||||
|
limit_except GET POST {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional: IP whitelist for Autotask
|
||||||
|
# Get Autotask IP ranges from their documentation
|
||||||
|
# allow 1.2.3.4/24; # Autotask IP range
|
||||||
|
# deny all;
|
||||||
|
|
||||||
|
# Proxy to Pulse app
|
||||||
|
proxy_pass http://pulse_app;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_send_timeout 10s;
|
||||||
|
proxy_read_timeout 10s;
|
||||||
|
|
||||||
|
# Buffer settings
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block all other paths
|
||||||
|
location / {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Setup Steps**
|
||||||
|
|
||||||
|
**1. Install Nginx**
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Install Certbot (for SSL)**
|
||||||
|
```bash
|
||||||
|
sudo apt install certbot python3-certbot-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Create Configuration**
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/nginx/sites-available/pulse-webhooks
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste the configuration above, adjusting:
|
||||||
|
- `server_name` to your domain
|
||||||
|
- `upstream pulse_app` to your Pulse app address
|
||||||
|
|
||||||
|
**4. Enable Site**
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/pulse-webhooks /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t # Test configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Get SSL Certificate**
|
||||||
|
```bash
|
||||||
|
sudo certbot --nginx -d webhooks.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**6. Start Nginx**
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable nginx
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**7. Test**
|
||||||
|
```bash
|
||||||
|
curl https://webhooks.yourdomain.com/api/webhooks/autotask
|
||||||
|
# Should return: {"status":"active"...}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 2: Cloudflare Tunnel (Zero Trust)
|
||||||
|
|
||||||
|
### **Architecture**
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet → Cloudflare → Cloudflared (Tunnel) → Pulse App
|
||||||
|
No open ports needed!
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Setup Steps**
|
||||||
|
|
||||||
|
**1. Install Cloudflared**
|
||||||
|
```bash
|
||||||
|
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
|
||||||
|
sudo dpkg -i cloudflared-linux-amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Login to Cloudflare**
|
||||||
|
```bash
|
||||||
|
cloudflared tunnel login
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Create Tunnel**
|
||||||
|
```bash
|
||||||
|
cloudflared tunnel create pulse-webhooks
|
||||||
|
# Note the tunnel ID shown
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Create Configuration**
|
||||||
|
|
||||||
|
Create `~/.cloudflared/config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tunnel: <TUNNEL_ID>
|
||||||
|
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
# Webhook endpoint only
|
||||||
|
- hostname: webhooks.yourdomain.com
|
||||||
|
path: /api/webhooks/autotask
|
||||||
|
service: http://localhost:3100
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
- hostname: webhooks.yourdomain.com
|
||||||
|
path: /api/webhooks/*
|
||||||
|
service: http://localhost:3100
|
||||||
|
|
||||||
|
# Catch-all (deny everything else)
|
||||||
|
- service: http_status:404
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Route DNS**
|
||||||
|
```bash
|
||||||
|
cloudflared tunnel route dns pulse-webhooks webhooks.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**6. Run Tunnel**
|
||||||
|
```bash
|
||||||
|
# Test first
|
||||||
|
cloudflared tunnel run pulse-webhooks
|
||||||
|
|
||||||
|
# If working, install as service
|
||||||
|
sudo cloudflared service install
|
||||||
|
sudo systemctl start cloudflared
|
||||||
|
sudo systemctl enable cloudflared
|
||||||
|
```
|
||||||
|
|
||||||
|
**7. Test**
|
||||||
|
```bash
|
||||||
|
curl https://webhooks.yourdomain.com/api/webhooks/autotask
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Cloudflare Tunnel Benefits**
|
||||||
|
|
||||||
|
- ✅ No firewall changes needed
|
||||||
|
- ✅ No open ports
|
||||||
|
- ✅ Free tier available
|
||||||
|
- ✅ Built-in DDoS protection
|
||||||
|
- ✅ Automatic SSL/TLS
|
||||||
|
- ✅ Easy to set up
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 3: Caddy (Automatic HTTPS)
|
||||||
|
|
||||||
|
### **Caddy Configuration**
|
||||||
|
|
||||||
|
Create `/etc/caddy/Caddyfile`:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
webhooks.yourdomain.com {
|
||||||
|
# Automatic HTTPS via Let's Encrypt
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
rate_limit {
|
||||||
|
zone webhook {
|
||||||
|
key {remote_host}
|
||||||
|
events 100
|
||||||
|
window 1m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only allow webhook endpoint
|
||||||
|
handle /api/webhooks/autotask {
|
||||||
|
reverse_proxy localhost:3100
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block everything else
|
||||||
|
handle {
|
||||||
|
respond 404
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/pulse-webhooks.log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Setup:**
|
||||||
|
```bash
|
||||||
|
# Install Caddy
|
||||||
|
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||||
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||||
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install caddy
|
||||||
|
|
||||||
|
# Configure
|
||||||
|
sudo nano /etc/caddy/Caddyfile
|
||||||
|
# Paste configuration above
|
||||||
|
|
||||||
|
# Start
|
||||||
|
sudo systemctl restart caddy
|
||||||
|
sudo systemctl enable caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 4: Docker with Traefik
|
||||||
|
|
||||||
|
If you're using Docker Compose, add Traefik as a reverse proxy:
|
||||||
|
|
||||||
|
### **docker-compose.yml**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
# Your existing Pulse app
|
||||||
|
app:
|
||||||
|
image: pulse-app
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
# No ports exposed!
|
||||||
|
|
||||||
|
# Traefik reverse proxy
|
||||||
|
traefik:
|
||||||
|
image: traefik:v2.10
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- ./traefik.yml:/traefik.yml:ro
|
||||||
|
- ./acme.json:/acme.json
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal:
|
||||||
|
driver: bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
### **traefik.yml**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
entryPoints:
|
||||||
|
web:
|
||||||
|
address: ":80"
|
||||||
|
http:
|
||||||
|
redirections:
|
||||||
|
entryPoint:
|
||||||
|
to: websecure
|
||||||
|
scheme: https
|
||||||
|
websecure:
|
||||||
|
address: ":443"
|
||||||
|
|
||||||
|
certificatesResolvers:
|
||||||
|
letsencrypt:
|
||||||
|
acme:
|
||||||
|
email: admin@yourdomain.com
|
||||||
|
storage: /acme.json
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: web
|
||||||
|
|
||||||
|
providers:
|
||||||
|
docker:
|
||||||
|
exposedByDefault: false
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
webhook-ratelimit:
|
||||||
|
rateLimit:
|
||||||
|
average: 100
|
||||||
|
burst: 20
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Update Pulse App Labels**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
app:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.webhooks.rule=Host(`webhooks.yourdomain.com`) && PathPrefix(`/api/webhooks/autotask`)"
|
||||||
|
- "traefik.http.routers.webhooks.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.webhooks.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.routers.webhooks.middlewares=webhook-ratelimit"
|
||||||
|
- "traefik.http.services.webhooks.loadbalancer.server.port=3100"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Recommendations
|
||||||
|
|
||||||
|
### **1. IP Whitelisting (If Possible)**
|
||||||
|
|
||||||
|
Get Autotask's IP ranges and whitelist them:
|
||||||
|
|
||||||
|
**Nginx:**
|
||||||
|
```nginx
|
||||||
|
# Inside location block
|
||||||
|
allow 1.2.3.4/24; # Autotask IP range
|
||||||
|
deny all;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cloudflare:**
|
||||||
|
Use Cloudflare Access rules to restrict by IP
|
||||||
|
|
||||||
|
### **2. Rate Limiting**
|
||||||
|
|
||||||
|
All configurations above include rate limiting:
|
||||||
|
- 100 requests per minute
|
||||||
|
- Burst of 20 requests
|
||||||
|
- Prevents abuse/DDoS
|
||||||
|
|
||||||
|
### **3. Monitoring**
|
||||||
|
|
||||||
|
Monitor webhook endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nginx access log
|
||||||
|
tail -f /var/log/nginx/pulse-webhooks-access.log
|
||||||
|
|
||||||
|
# Check for suspicious activity
|
||||||
|
grep -v "200\|201" /var/log/nginx/pulse-webhooks-access.log | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
### **4. Firewall Rules**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# UFW example - only allow 80/443
|
||||||
|
sudo ufw allow 80/tcp
|
||||||
|
sudo ufw allow 443/tcp
|
||||||
|
sudo ufw enable
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Your Setup
|
||||||
|
|
||||||
|
### **1. Health Check**
|
||||||
|
```bash
|
||||||
|
curl https://webhooks.yourdomain.com/api/webhooks/autotask
|
||||||
|
# Expected: {"status":"active"...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. Test Webhook POST**
|
||||||
|
```bash
|
||||||
|
curl -X POST https://webhooks.yourdomain.com/api/webhooks/autotask \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"eventId": "test_123",
|
||||||
|
"eventType": "create",
|
||||||
|
"entityType": "Tickets",
|
||||||
|
"entityId": 99999,
|
||||||
|
"eventTimestamp": "2026-01-24T10:00:00Z"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### **3. Verify Blocked Paths**
|
||||||
|
```bash
|
||||||
|
curl https://webhooks.yourdomain.com/
|
||||||
|
# Expected: 404 Not Found
|
||||||
|
|
||||||
|
curl https://webhooks.yourdomain.com/admin
|
||||||
|
# Expected: 404 Not Found
|
||||||
|
```
|
||||||
|
|
||||||
|
### **4. Test Rate Limiting**
|
||||||
|
```bash
|
||||||
|
# Send 150 requests quickly
|
||||||
|
for i in {1..150}; do
|
||||||
|
curl -s https://webhooks.yourdomain.com/api/webhooks/autotask > /dev/null
|
||||||
|
done
|
||||||
|
# Should see 429 Too Many Requests after ~100 requests
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Autotask Configuration
|
||||||
|
|
||||||
|
Once your endpoint is exposed:
|
||||||
|
|
||||||
|
1. Go to Autotask → Admin → Webhooks
|
||||||
|
2. Create webhook with URL: `https://webhooks.yourdomain.com/api/webhooks/autotask`
|
||||||
|
3. Test webhook from Autotask UI
|
||||||
|
4. Monitor logs to verify receipt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### **Webhook Not Received**
|
||||||
|
|
||||||
|
**Check 1: DNS Resolution**
|
||||||
|
```bash
|
||||||
|
nslookup webhooks.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check 2: SSL Certificate**
|
||||||
|
```bash
|
||||||
|
curl -v https://webhooks.yourdomain.com/api/webhooks/autotask
|
||||||
|
# Look for SSL handshake errors
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check 3: Proxy Logs**
|
||||||
|
```bash
|
||||||
|
# Nginx
|
||||||
|
tail -f /var/log/nginx/pulse-webhooks-error.log
|
||||||
|
|
||||||
|
# Cloudflare Tunnel
|
||||||
|
sudo journalctl -u cloudflared -f
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check 4: Firewall**
|
||||||
|
```bash
|
||||||
|
sudo ufw status
|
||||||
|
# Ensure 80/443 are allowed
|
||||||
|
```
|
||||||
|
|
||||||
|
### **502 Bad Gateway**
|
||||||
|
|
||||||
|
Pulse app is not reachable:
|
||||||
|
```bash
|
||||||
|
# Check Pulse app is running
|
||||||
|
docker ps | grep pulse-app
|
||||||
|
|
||||||
|
# Check port
|
||||||
|
netstat -tlnp | grep 3100
|
||||||
|
|
||||||
|
# Test direct connection
|
||||||
|
curl http://localhost:3100/api/webhooks/autotask
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Approach
|
||||||
|
|
||||||
|
**For Production: Nginx + Let's Encrypt**
|
||||||
|
- Most control
|
||||||
|
- Best performance
|
||||||
|
- Industry standard
|
||||||
|
|
||||||
|
**For Quick Setup: Cloudflare Tunnel**
|
||||||
|
- Fastest to set up
|
||||||
|
- No firewall changes
|
||||||
|
- Good for testing
|
||||||
|
|
||||||
|
**For Docker Users: Traefik**
|
||||||
|
- Integrates with Docker
|
||||||
|
- Automatic SSL
|
||||||
|
- Container-native
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
You need to expose **only** `/api/webhooks/autotask` to the internet for Autotask to reach it. Use a reverse proxy to:
|
||||||
|
|
||||||
|
1. ✅ Expose only the webhook endpoint
|
||||||
|
2. ✅ Add SSL/TLS encryption
|
||||||
|
3. ✅ Implement rate limiting
|
||||||
|
4. ✅ Optional IP whitelisting
|
||||||
|
5. ✅ Keep rest of application private
|
||||||
|
|
||||||
|
Choose the approach that fits your infrastructure best!
|
||||||
Loading…
Add table
Add a link
Reference in a new issue