Skip to content

Add Traefik Rate Limits #4

Add Traefik Rate Limits

Add Traefik Rate Limits #4

name: Production Stack Test
# Enable Buildkit and let compose use it to speed up image building
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
defaults:
run:
working-directory: ./
on:
pull_request:
branches: [ "master", "main" ]
paths:
- "compose/production/**"
- "production.yml"
- ".github/workflows/production-stack.yml"
push:
branches: [ "master", "main" ]
paths:
- "compose/production/**"
- "production.yml"
- ".github/workflows/production-stack.yml"
concurrency:
group: production-stack-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
production-stack:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Checkout Code Repository
uses: actions/checkout@v4.2.2
- name: Create Required Environment Files
run: |
mkdir -p .envs/.production
# Create minimal Django env file based on sample
cat > .envs/.production/.django << 'EOF'
# Core Django Security Settings
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_SECRET_KEY=test-secret-key-for-ci-only-not-secure
DJANGO_ADMIN_URL=admin/
DJANGO_SECURE_SSL_REDIRECT=false
# Django Username and Password for Initial Admin Login
DJANGO_SUPERUSER_PASSWORD=Openc0ntracts_test
DJANGO_SUPERUSER_EMAIL=test@opensource.legal
DJANGO_SUPERUSER_USERNAME=admin
# General
USE_DOCKER=yes
WEB_CONCURRENCY=4
IPYTHONDIR=/app/.ipython
DJANGO_ALLOWED_HOSTS=django,127.0.0.1,localhost,0.0.0.0,opencontracts.opensource.legal
DJANGO_WORKER_TIMEOUT=3600
# Application Configuration
USE_ANALYZER=false
CALLBACK_ROOT_URL_FOR_ANALYZER=http://django:8000
# AWS - disabled for testing
USE_AWS=false
AWS_ACCESS_KEY_ID=test-key
AWS_SECRET_ACCESS_KEY=test-secret
AWS_STORAGE_BUCKET_NAME=test-bucket
AWS_S3_REGION_NAME=us-east-1
# Redis
REDIS_URL=redis://redis:6379/0
# Celery
# Flower
CELERY_FLOWER_USER=test
CELERY_FLOWER_PASSWORD=test
# NLM Parser
NLM_INGESTOR_ACTIVE=false
# LLM SETTINGS
OPENAI_API_KEY=test-api-key-not-real
OPENAI_MODEL=gpt-4o
# AUTH0 - disabled for testing
USE_AUTH0=false
AUTH0_CLIENT_ID=test-client-id
AUTH0_API_AUDIENCE=https://opensource.legal/contracts
AUTH0_DOMAIN=test.auth0.com
AUTH0_M2M_MANAGEMENT_API_SECRET=test-secret
AUTH0_M2M_MANAGEMENT_API_ID=test-id
AUTH0_M2M_MANAGEMENT_GRANT_TYPE=client_credentials
# Docling
DOCLING_MODELS_PATH=/models/docling
SENTENCE_TRANSFORMER_MODELS_PATH=/models/sentence-transformers
EOF
# Create minimal Postgres env file
cat > .envs/.production/.postgres << 'EOF'
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=opencontracts
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
EOF
# Create minimal Frontend env file based on sample
cat > .envs/.production/.frontend << 'EOF'
OPEN_CONTRACTS_REACT_APP_APPLICATION_DOMAIN=opencontracts.opensource.legal
OPEN_CONTRACTS_REACT_APP_APPLICATION_CLIENT_ID=test-client-id
OPEN_CONTRACTS_REACT_APP_AUDIENCE=http://localhost:3000
OPEN_CONTRACTS_REACT_APP_API_ROOT_URL=http://django:8000
# Disabled for testing
OPEN_CONTRACTS_REACT_APP_USE_AUTH0=false
OPEN_CONTRACTS_REACT_APP_USE_ANALYZERS=false
OPEN_CONTRACTS_REACT_APP_ALLOW_IMPORTS=false
EOF
- name: Build Production Stack (Test Mode)
run: docker compose -f production.yml -f compose/test-production.yml build
- name: Start Production Stack (Test Mode)
run: |
docker compose -f production.yml -f compose/test-production.yml up -d
sleep 30 # Give services time to start
- name: Verify Stack Health
run: |
echo "=== Docker containers status ==="
docker compose -f production.yml -f compose/test-production.yml ps
echo "=== Container health checks ==="
for container in $(docker compose -f production.yml -f compose/test-production.yml ps -q); do
name=$(docker inspect -f '{{.Name}}' $container | sed 's/^\/*//')
echo "--- Health for $name ---"
docker logs --tail 10 $container 2>&1 || true
done
- name: Test Traefik Configuration
run: |
echo "=== Testing Traefik config parsing ==="
docker compose -f production.yml -f compose/test-production.yml logs traefik | grep -i "rate" || echo "No rate limiting logs yet"
echo "=== Testing basic connectivity ==="
# Test if traefik responds - through its exposed port 80
docker compose -f production.yml -f compose/test-production.yml exec -T django wget -qO- --header="Host: opencontracts.opensource.legal" http://traefik:80/ || echo "Expected - backend may not be ready"
- name: Test Rate Limiting
run: |
echo "=== Testing Frontend Rate Limits ==="
# Test frontend rate limiting through Traefik (port 80) from django container
success_count=0
rate_limited_count=0
for i in {1..250}; do
response=$(docker compose -f production.yml -f compose/test-production.yml exec -T django wget -qS --header="Host: opencontracts.opensource.legal" http://traefik:80/ -O /dev/null 2>&1 | grep -o "HTTP/1.1 [0-9]*" | cut -d' ' -f2 | head -1 || echo "000")
if [ "$response" = "200" ] || [ "$response" = "404" ] || [ "$response" = "502" ] || [ "$response" = "503" ]; then
success_count=$((success_count + 1))
elif [ "$response" = "429" ]; then
rate_limited_count=$((rate_limited_count + 1))
fi
# Small delay to avoid overwhelming
sleep 0.01
done
echo "Successful requests: $success_count"
echo "Rate limited requests: $rate_limited_count"
# We expect some rate limiting after burst is exhausted
if [ $rate_limited_count -gt 0 ]; then
echo "✅ Rate limiting is working - got $rate_limited_count rate limited responses"
else
echo "⚠️ No rate limiting detected - this might indicate an issue"
fi
echo "=== Testing API Rate Limits (lower threshold) ==="
api_success=0
api_rate_limited=0
# Wait a moment for rate limits to reset
sleep 5
for i in {1..100}; do
response=$(docker compose -f production.yml -f compose/test-production.yml exec -T django wget -qS --header="Host: opencontracts.opensource.legal" http://traefik:80/graphql -O /dev/null 2>&1 | grep -o "HTTP/1.1 [0-9]*" | cut -d' ' -f2 | head -1 || echo "000")
if [ "$response" = "200" ] || [ "$response" = "404" ] || [ "$response" = "502" ] || [ "$response" = "503" ]; then
api_success=$((api_success + 1))
elif [ "$response" = "429" ]; then
api_rate_limited=$((api_rate_limited + 1))
fi
sleep 0.01
done
echo "API successful requests: $api_success"
echo "API rate limited requests: $api_rate_limited"
- name: Check Redis Rate Limit Storage
run: |
echo "=== Checking Redis for rate limit data ==="
# Check if rate limiting data is being stored in Redis
docker compose -f production.yml -f compose/test-production.yml exec -T redis redis-cli -n 1 keys "*" | head -20
docker compose -f production.yml -f compose/test-production.yml exec -T redis redis-cli -n 1 info keyspace
- name: Capture Logs on Failure
if: failure()
run: |
echo "=== Capturing logs for debugging ==="
docker compose -f production.yml -f compose/test-production.yml logs --no-color > production-stack-logs.txt
- name: Upload Logs on Failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: production-stack-logs
path: production-stack-logs.txt
- name: Tear Down Stack
if: always()
run: docker compose -f production.yml -f compose/test-production.yml down -v