basic proxy ip detection test tool
This commit is contained in:
commit
d3cff15545
15 changed files with 1075 additions and 0 deletions
115
.gitignore
vendored
Normal file
115
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
bun.lockb
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Storybook build outputs
|
||||||
|
.out
|
||||||
|
.storybook-out
|
||||||
|
|
||||||
|
# Temporary folders
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Kubernetes secrets (keep template)
|
||||||
|
k8s-secret-production.yaml
|
||||||
|
|
||||||
|
# Local development
|
||||||
|
*.local
|
||||||
42
.gitlab-ci-simple.yml
Normal file
42
.gitlab-ci-simple.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Simple GitLab CI/CD Pipeline - Build and Deploy only
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
IMAGE_NAME: $CI_REGISTRY_IMAGE
|
||||||
|
IMAGE_TAG: $CI_COMMIT_SHA
|
||||||
|
|
||||||
|
# Build and push Docker image
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
image: docker:24.0.5
|
||||||
|
services:
|
||||||
|
- docker:24.0.5-dind
|
||||||
|
before_script:
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
variables:
|
||||||
|
DOCKER_TLS_CERTDIR: "/certs"
|
||||||
|
script:
|
||||||
|
- docker info
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
- docker pull $CI_REGISTRY_IMAGE:latest || true
|
||||||
|
- docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:latest
|
||||||
|
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
# Deploy to production
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
image: ictu/sshpass
|
||||||
|
script:
|
||||||
|
- sshpass -p c4c5f47918a6310d79de188465218b440c69570c ssh -o StrictHostKeyChecking=no kubeuser@stare.gg 'kubectl rollout restart deployments/stare-site -n stare'
|
||||||
|
|
||||||
|
environment:
|
||||||
|
name: production
|
||||||
|
only:
|
||||||
|
- master
|
||||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Use the official Bun image as base - much smaller than Node.js
|
||||||
|
FROM oven/bun:1-slim as base
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package.json bun.lockb* ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY src/ ./src/
|
||||||
|
|
||||||
|
# Expose the port
|
||||||
|
EXPOSE 9999
|
||||||
|
|
||||||
|
# Create non-root user for security
|
||||||
|
RUN addgroup --system --gid 1001 bunuser && \
|
||||||
|
adduser --system --uid 1001 bunuser
|
||||||
|
|
||||||
|
# Change ownership of the app directory
|
||||||
|
RUN chown -R bunuser:bunuser /app
|
||||||
|
USER bunuser
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD bun --version || exit 1
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["bun", "run", "start"]
|
||||||
81
KUBERNETES.md
Normal file
81
KUBERNETES.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Kubernetes Deployment
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Kubernetes cluster running
|
||||||
|
- `kubectl` configured to access your cluster
|
||||||
|
- Docker registry access (for GitLab CI)
|
||||||
|
|
||||||
|
### Deploy to Kubernetes
|
||||||
|
|
||||||
|
**Option 1: Use deployment script**
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
deploy.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Manual deployment**
|
||||||
|
```bash
|
||||||
|
# 1. Create secret for API key
|
||||||
|
kubectl apply -f k8s-secret.yaml
|
||||||
|
|
||||||
|
# 2. Deploy application
|
||||||
|
kubectl apply -f k8s-deployment.yaml
|
||||||
|
|
||||||
|
# 3. Optional: Deploy ingress for external access
|
||||||
|
kubectl apply -f k8s-ingress.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test the deployment
|
||||||
|
```bash
|
||||||
|
# Port forward to test locally
|
||||||
|
kubectl port-forward service/proxy-detection-service 8080:80
|
||||||
|
|
||||||
|
# Test health endpoint (no auth required)
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
|
||||||
|
# Test main endpoint (requires API key)
|
||||||
|
curl -H "X-API-Key: bd406bf53ddc6abe1d9de5907830a955" http://localhost:8080/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
#### Update API Key
|
||||||
|
1. Generate base64 encoded API key:
|
||||||
|
```bash
|
||||||
|
echo -n "your-new-api-key" | base64
|
||||||
|
```
|
||||||
|
2. Update `k8s-secret.yaml` with the new value
|
||||||
|
3. Apply changes:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f k8s-secret.yaml
|
||||||
|
kubectl rollout restart deployment/proxy-detection-api
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Update Image
|
||||||
|
Update image in `k8s-deployment.yaml` or use kubectl:
|
||||||
|
```bash
|
||||||
|
kubectl set image deployment/proxy-detection-api proxy-detection-api=your-registry/proxy-detection-api:new-tag
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitLab CI/CD
|
||||||
|
|
||||||
|
The `.gitlab-ci-simple.yml` provides:
|
||||||
|
- **Build stage**: Builds and pushes Docker image to GitLab registry
|
||||||
|
- **Deploy stage**: Updates Kubernetes deployment (manual trigger)
|
||||||
|
|
||||||
|
Replace `.gitlab-ci.yml` with `.gitlab-ci-simple.yml` for the minimal pipeline.
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
The deployment is configured with minimal resources:
|
||||||
|
- **Requests**: 64Mi RAM, 50m CPU
|
||||||
|
- **Limits**: 128Mi RAM, 100m CPU
|
||||||
|
- **Replicas**: 2 (for basic high availability)
|
||||||
|
|
||||||
|
Adjust these values in `k8s-deployment.yaml` based on your needs.
|
||||||
221
README.md
Normal file
221
README.md
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
# Proxy Detection API
|
||||||
|
|
||||||
|
A simple Bun and Fastify API that extracts IP addresses from HTTP request headers to detect potential proxy leaks.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Built-in IP Detection**: Fastify's `trustProxy` automatically parses proxy headers
|
||||||
|
- **Full IP Chain**: Get the complete proxy chain, not just the client IP
|
||||||
|
- **Custom Header Analysis**: Extracts IPs from 17+ different proxy headers
|
||||||
|
- **No External Dependencies**: Works entirely by analyzing request headers
|
||||||
|
- **Fast Performance**: Fastify is one of the fastest Node.js frameworks
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production
|
||||||
|
```bash
|
||||||
|
bun run start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Deployment
|
||||||
|
|
||||||
|
### Quick Start with Docker
|
||||||
|
```bash
|
||||||
|
# Build the image
|
||||||
|
npm run docker:build
|
||||||
|
|
||||||
|
# Run the container
|
||||||
|
npm run docker:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Docker Compose
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
npm run docker:dev
|
||||||
|
|
||||||
|
# Production (with nginx reverse proxy)
|
||||||
|
npm run docker:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Docker Commands
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
docker build -t proxy-detection-api .
|
||||||
|
|
||||||
|
# Run
|
||||||
|
docker run -p 9999:9999 proxy-detection-api
|
||||||
|
|
||||||
|
# Run with environment variables
|
||||||
|
docker run -p 9999:9999 -e NODE_ENV=production proxy-detection-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitLab CI/CD
|
||||||
|
|
||||||
|
This project includes a complete GitLab CI/CD pipeline that:
|
||||||
|
|
||||||
|
1. **Tests** - Runs tests and TypeScript compilation
|
||||||
|
2. **Builds** - Creates Docker images and pushes to GitLab Container Registry
|
||||||
|
3. **Deploys** - Deploys to staging/production environments
|
||||||
|
|
||||||
|
### Pipeline Stages:
|
||||||
|
- `test` - Runs on MRs and main branch
|
||||||
|
- `build` - Builds Docker images on main/develop/tags
|
||||||
|
- `deploy_staging` - Manual deployment to staging (develop branch)
|
||||||
|
- `deploy_production` - Manual deployment to production (main branch/tags)
|
||||||
|
|
||||||
|
### Required GitLab Variables:
|
||||||
|
- `CI_REGISTRY_USER` - GitLab registry username (auto-provided)
|
||||||
|
- `CI_REGISTRY_PASSWORD` - GitLab registry password (auto-provided)
|
||||||
|
- Add your deployment-specific variables in GitLab CI/CD settings
|
||||||
|
|
||||||
|
The API will be available at `http://localhost:9999`
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
|
||||||
|
#### `GET /`
|
||||||
|
Returns API information and available endpoints.
|
||||||
|
|
||||||
|
#### `GET /detect`
|
||||||
|
Extracts all IP addresses found in request headers with full proxy chain detection.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"clientIP": "203.0.113.45", // Fastify's auto-detected client IP
|
||||||
|
"ipChain": ["203.0.113.45", "192.168.1.100"], // Complete proxy chain
|
||||||
|
"foundIPs": ["192.168.1.100", "203.0.113.45"], // IPs found by custom parser
|
||||||
|
"totalFound": 2,
|
||||||
|
"headersSources": {
|
||||||
|
"x-forwarded-for": ["192.168.1.100", "203.0.113.45"],
|
||||||
|
"x-real-ip": ["203.0.113.45"]
|
||||||
|
},
|
||||||
|
"allHeaders": { ... },
|
||||||
|
"timestamp": 1672531200000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's New with Fastify
|
||||||
|
|
||||||
|
### Trust Proxy Support
|
||||||
|
Fastify automatically handles proxy headers when `trustProxy: true` is enabled:
|
||||||
|
- Parses `X-Forwarded-For` header automatically
|
||||||
|
- Provides `request.ip` (client IP) and `request.ips` (full chain)
|
||||||
|
- More secure than manual header parsing
|
||||||
|
|
||||||
|
### Built-in IP Chain Detection
|
||||||
|
```javascript
|
||||||
|
// Fastify automatically provides:
|
||||||
|
request.ip // "203.0.113.45" (real client)
|
||||||
|
request.ips // ["203.0.113.45", "10.0.0.1", "192.168.1.100"] (full chain)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development (with auto-reload)
|
||||||
|
bun run dev
|
||||||
|
|
||||||
|
# Production
|
||||||
|
bun run start
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### `GET /`
|
||||||
|
Returns API information and available endpoints.
|
||||||
|
|
||||||
|
### `GET /detect`
|
||||||
|
Performs a comprehensive proxy detection check:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"yourIP": "1.2.3.4",
|
||||||
|
"externalIPs": [
|
||||||
|
{
|
||||||
|
"ip": "1.2.3.4",
|
||||||
|
"source": "ipify",
|
||||||
|
"timestamp": 1234567890
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isProxyWorking": true,
|
||||||
|
"leakedIPs": [],
|
||||||
|
"timestamp": 1234567890
|
||||||
|
},
|
||||||
|
"message": "Proxy is working correctly"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /quick`
|
||||||
|
Quick IP check using a single service:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ip": "1.2.3.4",
|
||||||
|
"timestamp": 1234567890
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /headers`
|
||||||
|
Shows request headers and client IP information:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clientIP": "1.2.3.4",
|
||||||
|
"headers": {
|
||||||
|
"x-forwarded-for": "1.2.3.4",
|
||||||
|
"user-agent": "Mozilla/5.0..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Multiple IP Sources**: Queries several IP detection services (ipify, httpbin, ipapi, jsonip)
|
||||||
|
2. **Comparison**: Compares results to detect inconsistencies
|
||||||
|
3. **Leak Detection**: If different services return different IPs, it indicates a potential proxy leak
|
||||||
|
4. **Analysis**: Determines if your proxy is working correctly
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- Test if your VPN/proxy is working
|
||||||
|
- Detect DNS leaks
|
||||||
|
- Monitor IP consistency across services
|
||||||
|
- Verify anonymity tools
|
||||||
|
|
||||||
|
## Example Response (Proxy Working)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"yourIP": "192.168.1.100",
|
||||||
|
"isProxyWorking": true,
|
||||||
|
"leakedIPs": [],
|
||||||
|
"message": "Proxy is working correctly"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Response (IP Leak Detected)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"yourIP": "192.168.1.100",
|
||||||
|
"isProxyWorking": false,
|
||||||
|
"leakedIPs": ["203.0.113.5"],
|
||||||
|
"message": "Potential IP leak detected: 203.0.113.5"
|
||||||
|
}
|
||||||
|
```
|
||||||
126
bun.lock
Normal file
126
bun.lock
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "proxy-detection-api",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cors": "^11.0.1",
|
||||||
|
"fastify": "^5.3.3",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^22.15.30",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.2", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ=="],
|
||||||
|
|
||||||
|
"@fastify/cors": ["@fastify/cors@11.0.1", "", { "dependencies": { "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ=="],
|
||||||
|
|
||||||
|
"@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
|
||||||
|
|
||||||
|
"@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="],
|
||||||
|
|
||||||
|
"@fastify/forwarded": ["@fastify/forwarded@3.0.0", "", {}, "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA=="],
|
||||||
|
|
||||||
|
"@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
|
||||||
|
|
||||||
|
"@fastify/proxy-addr": ["@fastify/proxy-addr@5.0.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@22.15.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA=="],
|
||||||
|
|
||||||
|
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
|
||||||
|
|
||||||
|
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||||
|
|
||||||
|
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||||
|
|
||||||
|
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
||||||
|
|
||||||
|
"avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
|
||||||
|
|
||||||
|
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||||
|
|
||||||
|
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||||
|
|
||||||
|
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||||
|
|
||||||
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
|
"fast-json-stringify": ["fast-json-stringify@6.0.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^2.0.0", "rfdc": "^1.2.0" } }, "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg=="],
|
||||||
|
|
||||||
|
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
||||||
|
|
||||||
|
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
|
||||||
|
|
||||||
|
"fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
|
||||||
|
|
||||||
|
"fastify": ["fastify@5.3.3", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-nCBiBCw9q6jPx+JJNVgO8JVnTXeUyrGcyTKPQikRkA/PanrFcOIo4R+ZnLeOLPZPGgzjomqfVarzE0kYx7qWiQ=="],
|
||||||
|
|
||||||
|
"fastify-plugin": ["fastify-plugin@5.0.1", "", {}, "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ=="],
|
||||||
|
|
||||||
|
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||||
|
|
||||||
|
"find-my-way": ["find-my-way@9.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg=="],
|
||||||
|
|
||||||
|
"ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="],
|
||||||
|
|
||||||
|
"json-schema-ref-resolver": ["json-schema-ref-resolver@2.0.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q=="],
|
||||||
|
|
||||||
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
|
"light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
|
||||||
|
|
||||||
|
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
||||||
|
|
||||||
|
"pino": ["pino@9.7.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg=="],
|
||||||
|
|
||||||
|
"pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
|
||||||
|
|
||||||
|
"pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="],
|
||||||
|
|
||||||
|
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||||
|
|
||||||
|
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||||
|
|
||||||
|
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
||||||
|
|
||||||
|
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||||
|
|
||||||
|
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
|
||||||
|
|
||||||
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
|
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||||
|
|
||||||
|
"safe-regex2": ["safe-regex2@5.0.0", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="],
|
||||||
|
|
||||||
|
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||||
|
|
||||||
|
"secure-json-parse": ["secure-json-parse@4.0.0", "", {}, "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA=="],
|
||||||
|
|
||||||
|
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||||
|
|
||||||
|
"sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
|
||||||
|
|
||||||
|
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||||
|
|
||||||
|
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
|
||||||
|
|
||||||
|
"toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
|
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
32
deploy.bat
Normal file
32
deploy.bat
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
@echo off
|
||||||
|
echo 🚀 Deploying Proxy Detection API to Kubernetes...
|
||||||
|
|
||||||
|
REM Apply secret (API key)
|
||||||
|
echo 📋 Creating secret...
|
||||||
|
kubectl apply -f k8s-secret.yaml
|
||||||
|
|
||||||
|
REM Apply deployment and service
|
||||||
|
echo 🔧 Deploying application...
|
||||||
|
kubectl apply -f k8s-deployment.yaml
|
||||||
|
|
||||||
|
REM Optional: Apply ingress
|
||||||
|
set /p deploy_ingress="Do you want to deploy ingress? (y/N): "
|
||||||
|
if /i "%deploy_ingress%"=="y" (
|
||||||
|
echo 🌐 Deploying ingress...
|
||||||
|
kubectl apply -f k8s-ingress.yaml
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check deployment status
|
||||||
|
echo 📊 Checking deployment status...
|
||||||
|
kubectl rollout status deployment/proxy-detection-api
|
||||||
|
|
||||||
|
REM Show service info
|
||||||
|
echo 📋 Service information:
|
||||||
|
kubectl get service proxy-detection-service
|
||||||
|
|
||||||
|
echo ✅ Deployment completed!
|
||||||
|
echo 💡 To test the API:
|
||||||
|
echo kubectl port-forward service/proxy-detection-service 8080:80
|
||||||
|
echo curl http://localhost:8080/health
|
||||||
|
|
||||||
|
pause
|
||||||
33
deploy.sh
Normal file
33
deploy.sh
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple Kubernetes deployment script
|
||||||
|
echo "🚀 Deploying Proxy Detection API to Kubernetes..."
|
||||||
|
|
||||||
|
# Apply secret (API key)
|
||||||
|
echo "📋 Creating secret..."
|
||||||
|
kubectl apply -f k8s-secret.yaml
|
||||||
|
|
||||||
|
# Apply deployment and service
|
||||||
|
echo "🔧 Deploying application..."
|
||||||
|
kubectl apply -f k8s-deployment.yaml
|
||||||
|
|
||||||
|
# Optional: Apply ingress
|
||||||
|
read -p "Do you want to deploy ingress? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "🌐 Deploying ingress..."
|
||||||
|
kubectl apply -f k8s-ingress.yaml
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check deployment status
|
||||||
|
echo "📊 Checking deployment status..."
|
||||||
|
kubectl rollout status deployment/proxy-detection-api
|
||||||
|
|
||||||
|
# Show service info
|
||||||
|
echo "📋 Service information:"
|
||||||
|
kubectl get service proxy-detection-service
|
||||||
|
|
||||||
|
echo "✅ Deployment completed!"
|
||||||
|
echo "💡 To test the API:"
|
||||||
|
echo " kubectl port-forward service/proxy-detection-service 8080:80"
|
||||||
|
echo " curl http://localhost:8080/health"
|
||||||
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
proxy-detection:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "9999:9999"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9999/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- proxy-net
|
||||||
|
|
||||||
|
# Optional: Add a reverse proxy for production
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
# - ./ssl:/etc/nginx/ssl:ro # Add SSL certificates here
|
||||||
|
depends_on:
|
||||||
|
- proxy-detection
|
||||||
|
networks:
|
||||||
|
- proxy-net
|
||||||
|
profiles:
|
||||||
|
- production
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy-net:
|
||||||
|
driver: bridge
|
||||||
69
k8s-deployment.yaml
Normal file
69
k8s-deployment.yaml
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: proxy-detection-api
|
||||||
|
labels:
|
||||||
|
app: proxy-detection-api
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: proxy-detection-api
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: proxy-detection-api
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: proxy-detection-api
|
||||||
|
image: your-registry/proxy-detection-api:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 2424
|
||||||
|
env:
|
||||||
|
- name: PORT
|
||||||
|
value: "2424"
|
||||||
|
- name: HOST
|
||||||
|
value: "0.0.0.0"
|
||||||
|
- name: NODE_ENV
|
||||||
|
value: "production"
|
||||||
|
- name: TRUST_PROXY
|
||||||
|
value: "true"
|
||||||
|
- name: API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: proxy-detection-secret
|
||||||
|
key: api-key
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 2424
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 2424
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: proxy-detection-service
|
||||||
|
labels:
|
||||||
|
app: proxy-detection-api
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: proxy-detection-api
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 2424
|
||||||
|
protocol: TCP
|
||||||
|
type: ClusterIP
|
||||||
18
k8s-ingress.yaml
Normal file
18
k8s-ingress.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: proxy-detection-ingress
|
||||||
|
labels:
|
||||||
|
app: proxy-detection-api
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: proxy-detection.stare.gg
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: proxy-detection-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
9
k8s-secret.yaml
Normal file
9
k8s-secret.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: proxy-detection-secret
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
# Base64 encoded API key (replace with your actual base64 encoded API key)
|
||||||
|
# To encode: echo -n "your-api-key" | base64
|
||||||
|
api-key: YmQ0MDZiZjUzZGRjNmFiZTFkOWRlNTkwNzgzMGE5NTU=
|
||||||
25
package.json
Normal file
25
package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "proxy-detection-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^22.15.30",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"description": "Simple API to detect if IP is exposed when using a proxy",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun run --watch src/index.ts",
|
||||||
|
"start": "bun run src/index.ts",
|
||||||
|
"build": "bun build src/index.ts --outdir ./dist --target bun",
|
||||||
|
"test": "echo \"No tests specified yet\" && exit 0",
|
||||||
|
"docker:build": "docker build -t proxy-detection-api .",
|
||||||
|
"docker:run": "docker run -p 9999:9999 proxy-detection-api",
|
||||||
|
"docker:dev": "docker-compose up --build",
|
||||||
|
"docker:prod": "docker-compose --profile production up -d"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cors": "^11.0.1",
|
||||||
|
"fastify": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/index.ts
Normal file
134
src/index.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { extractIPsFromHeaders } from './proxyService';
|
||||||
|
|
||||||
|
// Type definitions
|
||||||
|
interface ApiResponse {
|
||||||
|
success: boolean;
|
||||||
|
clientIP: string;
|
||||||
|
ipChain: string[];
|
||||||
|
foundIPs: string[];
|
||||||
|
totalFound: number;
|
||||||
|
headersSources: Record<string, string[]>;
|
||||||
|
allHeaders: Record<string, string>;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment configuration
|
||||||
|
const API_KEY = process.env.API_KEY;
|
||||||
|
const PORT = parseInt(process.env.PORT || '2424');
|
||||||
|
const HOST = process.env.HOST || '0.0.0.0';
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'production';
|
||||||
|
|
||||||
|
const objs: ApiResponse[] = []
|
||||||
|
|
||||||
|
// Create Fastify instance with trust proxy enabled
|
||||||
|
const fastify = Fastify({
|
||||||
|
logger: NODE_ENV === 'development',
|
||||||
|
trustProxy: process.env.TRUST_PROXY === 'true' || true
|
||||||
|
});
|
||||||
|
|
||||||
|
// API Key authentication middleware
|
||||||
|
fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
// Skip auth for health check endpoint
|
||||||
|
if (request.url === '/health') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = request.headers['x-api-key'] || (request.query as Record<string, any>)?.api_key;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
reply.code(401).send({
|
||||||
|
error: 'API key required',
|
||||||
|
message: 'Please provide API key in X-API-Key header or api_key query parameter'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKey !== API_KEY) {
|
||||||
|
reply.code(403).send({
|
||||||
|
error: 'Invalid API key',
|
||||||
|
message: 'The provided API key is not valid'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to get full IP chain
|
||||||
|
function getFullIPChain(request: FastifyRequest): { clientIP: string, ipChain: string[] } {
|
||||||
|
// Fastify automatically parses X-Forwarded-For when trustProxy is enabled
|
||||||
|
const clientIP = request.ip || 'unknown';
|
||||||
|
const ipChain = request.ips || []; // Full chain of IPs
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientIP,
|
||||||
|
ipChain: ipChain.length > 0 ? ipChain : [clientIP]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
const start = async () => {
|
||||||
|
try {
|
||||||
|
// Register CORS plugin
|
||||||
|
await fastify.register(import('@fastify/cors'), {
|
||||||
|
origin: true,
|
||||||
|
methods: ['GET', 'POST', 'OPTIONS']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check endpoint (bypasses authentication)
|
||||||
|
fastify.get('/health', async () => {
|
||||||
|
return {
|
||||||
|
status: 'healthy',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
uptime: process.uptime(),
|
||||||
|
version: '1.0.0'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Random endpoint for testing
|
||||||
|
fastify.get('/random', async () => {
|
||||||
|
return objs[Math.round(objs.length * Math.random())] || { message: 'No data yet' };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Main detection endpoint - extracts all IPs from headers
|
||||||
|
fastify.get('/', async (request) => {
|
||||||
|
const headers = request.headers;
|
||||||
|
const normalizedHeaders = Object.fromEntries(
|
||||||
|
Object.entries(headers).map(([key, value]) => [
|
||||||
|
key.toLowerCase(),
|
||||||
|
typeof value === 'string' ? value : String(value || '')
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = extractIPsFromHeaders(normalizedHeaders);
|
||||||
|
const ipInfo = getFullIPChain(request);
|
||||||
|
const obj = {
|
||||||
|
success: true,
|
||||||
|
clientIP: ipInfo.clientIP, // Fastify's auto-detected client IP
|
||||||
|
ipChain: ipInfo.ipChain, // Full proxy chain
|
||||||
|
foundIPs: result.ips, // IPs found by our custom parser
|
||||||
|
totalFound: result.ips.length,
|
||||||
|
headersSources: result.sources,
|
||||||
|
allHeaders: normalizedHeaders,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
if( objs.length > 1000) {
|
||||||
|
objs.shift();
|
||||||
|
}
|
||||||
|
objs.push(obj);
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastify.listen({ port: PORT, host: HOST });
|
||||||
|
console.log(`🚀 Proxy Detection API running on http://localhost:${PORT}`);
|
||||||
|
console.log(`📍 Available endpoints:`);
|
||||||
|
console.log(` GET /health - Health check (no auth required)`);
|
||||||
|
console.log(` GET / - Extract all IP addresses from request headers`);
|
||||||
|
console.log(` GET /random - Get random cached result`);
|
||||||
|
console.log(`🔑 API Key required for protected endpoints`);
|
||||||
|
} catch (err) {
|
||||||
|
fastify.log.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start();
|
||||||
100
src/proxyService.ts
Normal file
100
src/proxyService.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
export interface HeaderIPResult {
|
||||||
|
ips: string[];
|
||||||
|
sources: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractIPsFromHeaders(headers: Record<string, string | undefined>): HeaderIPResult {
|
||||||
|
const ips: string[] = [];
|
||||||
|
const sources: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
// Common headers that might contain IP addresses
|
||||||
|
const ipHeaders = [
|
||||||
|
'x-forwarded-for',
|
||||||
|
'x-real-ip',
|
||||||
|
'x-client-ip',
|
||||||
|
'x-cluster-client-ip',
|
||||||
|
'x-forwarded',
|
||||||
|
'forwarded-for',
|
||||||
|
'forwarded',
|
||||||
|
'cf-connecting-ip',
|
||||||
|
'true-client-ip',
|
||||||
|
'x-originating-ip',
|
||||||
|
'x-remote-ip',
|
||||||
|
'x-remote-addr',
|
||||||
|
'client-ip',
|
||||||
|
'remote-addr',
|
||||||
|
'http-client-ip',
|
||||||
|
'http-x-forwarded-for',
|
||||||
|
'http-x-real-ip'
|
||||||
|
];
|
||||||
|
|
||||||
|
// IP regex pattern (basic IPv4 validation)
|
||||||
|
const ipRegex = /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g;
|
||||||
|
|
||||||
|
// Check each header for IP addresses
|
||||||
|
for (const [headerName, headerValue] of Object.entries(headers)) {
|
||||||
|
if (!headerValue) continue;
|
||||||
|
|
||||||
|
// Check if this is a known IP header
|
||||||
|
if (ipHeaders.includes(headerName.toLowerCase())) {
|
||||||
|
const foundIPs = extractIPsFromValue(headerValue);
|
||||||
|
if (foundIPs.length > 0) {
|
||||||
|
sources[headerName] = foundIPs;
|
||||||
|
ips.push(...foundIPs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for IP patterns in any header value
|
||||||
|
const matches = headerValue.match(ipRegex);
|
||||||
|
if (matches) {
|
||||||
|
const validIPs = matches.filter(isValidIP);
|
||||||
|
if (validIPs.length > 0) {
|
||||||
|
if (!sources[headerName]) {
|
||||||
|
sources[headerName] = [];
|
||||||
|
}
|
||||||
|
sources[headerName].push(...validIPs);
|
||||||
|
ips.push(...validIPs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicates while preserving order
|
||||||
|
const uniqueIPs = [...new Set(ips)];
|
||||||
|
|
||||||
|
return {
|
||||||
|
ips: uniqueIPs,
|
||||||
|
sources
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractIPsFromValue(value: string): string[] {
|
||||||
|
const ips: string[] = [];
|
||||||
|
|
||||||
|
// Handle comma-separated values (common in X-Forwarded-For)
|
||||||
|
const parts = value.split(',').map(part => part.trim());
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
// Handle "for=" syntax in Forwarded header
|
||||||
|
if (part.startsWith('for=')) {
|
||||||
|
const ip = part.substring(4).replace(/"/g, '');
|
||||||
|
if (isValidIP(ip)) {
|
||||||
|
ips.push(ip);
|
||||||
|
}
|
||||||
|
} else if (isValidIP(part)) {
|
||||||
|
ips.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidIP(ip: string): boolean {
|
||||||
|
// Basic IPv4 validation
|
||||||
|
const parts = ip.split('.');
|
||||||
|
if (parts.length !== 4) return false;
|
||||||
|
|
||||||
|
return parts.every(part => {
|
||||||
|
const num = parseInt(part, 10);
|
||||||
|
return !isNaN(num) && num >= 0 && num <= 255;
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue