mirror of
https://github.com/r3bo0tbx1/tor-guard-relay.git
synced 2026-04-06 00:32:04 +02:00
feat(security): enforce localhost-only binding for internal services (v1.0.2)
BREAKING CHANGE: Internal services now bind to 127.0.0.1 by default This release enforces strict port security with a two-tier model: - Public: 9001 (ORPort), 9030 (DirPort) - Internal: 9035+ (metrics, health, dashboard) - localhost-only Changes: - CHANGELOG.md: Updated version entries, aligned to v1.0.2 - README.md: Updated version references and deployment examples - SECURITY.md: Enhanced with network architecture and port policy - relay-status.sh: Added port security validation (v1.0.2) - integration-check.sh: Added port/version validation phases (v1.0.2) Security Improvements: - Prevents unauthorized external access to internal endpoints - Adds reverse proxy, SSH tunnel, and VPN access guidance - Includes comprehensive migration guide for existing deployments - Adds automated security validation in diagnostic tools Migration Required: If accessing metrics/health externally, use reverse proxy with auth, SSH tunneling, or VPN. See SECURITY.md for details. Version: 1.0.2
This commit is contained in:
46
.github/workflows/release.yml
vendored
46
.github/workflows/release.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: 📥 Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Full history for tag detection
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 🔍 Detect Version & Build Type
|
||||
id: version
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
with:
|
||||
platforms: arm64,amd64
|
||||
|
||||
- name: <EFBFBD>️ Set up Docker Buildx
|
||||
- name: 🏗️ Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 🏷️ Generate Docker Tags
|
||||
@@ -198,21 +198,49 @@ jobs:
|
||||
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}"
|
||||
fi
|
||||
|
||||
echo "Validating image: ${IMAGE_TAG}"
|
||||
echo "🔍 Validating image: ${IMAGE_TAG}"
|
||||
|
||||
# Pull and verify image exists
|
||||
echo "📦 Pulling image..."
|
||||
docker pull "${IMAGE_TAG}" || exit 1
|
||||
|
||||
# Verify Alpine base
|
||||
echo "🐧 Checking Alpine base..."
|
||||
docker run --rm "${IMAGE_TAG}" cat /etc/os-release | grep -i alpine || exit 1
|
||||
|
||||
# Verify build info exists
|
||||
echo "📋 Checking build metadata..."
|
||||
docker run --rm "${IMAGE_TAG}" cat /build-info.txt || exit 1
|
||||
|
||||
# Verify Tor is installed
|
||||
docker run --rm "${IMAGE_TAG}" tor --version || exit 1
|
||||
# Verify Tor is installed and functional
|
||||
echo "🧅 Verifying Tor installation..."
|
||||
TOR_VERSION=$(docker run --rm "${IMAGE_TAG}" tor --version | head -1)
|
||||
if [ -z "$TOR_VERSION" ]; then
|
||||
echo "❌ Tor version check failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Tor version: $TOR_VERSION"
|
||||
|
||||
echo "✅ Image validation successful"
|
||||
# Verify required tools exist
|
||||
echo "🛠️ Checking diagnostic tools..."
|
||||
for tool in health metrics status fingerprint view-logs net-check setup dashboard; do
|
||||
if docker run --rm "${IMAGE_TAG}" sh -c "command -v $tool" > /dev/null 2>&1; then
|
||||
echo " ✅ $tool found"
|
||||
else
|
||||
echo " ⚠️ $tool not found (may be optional)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify entrypoint is executable
|
||||
echo "🔧 Checking entrypoint..."
|
||||
docker run --rm "${IMAGE_TAG}" sh -c "test -x /usr/local/bin/docker-entrypoint.sh" || exit 1
|
||||
|
||||
# Verify permissions on critical directories
|
||||
echo "🔒 Checking permissions..."
|
||||
docker run --rm "${IMAGE_TAG}" sh -c "ls -ld /var/lib/tor | grep '^d.*tor tor'" || exit 1
|
||||
|
||||
echo ""
|
||||
echo "✅ All validation checks passed"
|
||||
|
||||
release-notes:
|
||||
name: 📝 Generate Release Notes
|
||||
@@ -321,9 +349,9 @@ jobs:
|
||||
\`\`\`
|
||||
|
||||
## 📚 Documentation
|
||||
- [Deployment Guide](https://github.com/${{ github.repository }}/blob/main/DEPLOYMENT.md)
|
||||
- [Backup Guide](https://github.com/${{ github.repository }}/blob/main/BACKUP.md)
|
||||
- [Performance Tuning](https://github.com/${{ github.repository }}/blob/main/PERFORMANCE.md)
|
||||
- [Deployment Guide](https://github.com/${{ github.repository }}/blob/main/docs/DEPLOYMENT.md)
|
||||
- [Backup Guide](https://github.com/${{ github.repository }}/blob/main/docs/BACKUP.md)
|
||||
- [Performance Tuning](https://github.com/${{ github.repository }}/blob/main/docs/PERFORMANCE.md)
|
||||
EOF
|
||||
|
||||
- name: ✅ Workflow Complete
|
||||
|
||||
311
.github/workflows/validate.yml
vendored
311
.github/workflows/validate.yml
vendored
@@ -14,7 +14,8 @@ on:
|
||||
- 'Dockerfile'
|
||||
- 'docker-entrypoint.sh'
|
||||
- 'tools/**'
|
||||
- '.github/workflows/build.yml'
|
||||
- '.github/workflows/release.yml'
|
||||
- '.github/workflows/validate.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,63 +27,108 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: 🔍 Lint Dockerfile
|
||||
- name: 🔍 Lint Dockerfile with Hadolint
|
||||
uses: hadolint/hadolint-action@v3.1.0
|
||||
with:
|
||||
dockerfile: Dockerfile
|
||||
failure-threshold: warning
|
||||
|
||||
- name: 🔍 Validate Dockerfile Syntax
|
||||
run: |
|
||||
# Check Dockerfile syntax
|
||||
docker build --dry-run . || exit 1
|
||||
echo "✓ Dockerfile syntax valid"
|
||||
echo "Validating Dockerfile build context..."
|
||||
docker build --no-cache --target builder -t tor-relay-test . 2>&1 | tee /tmp/docker-build.log || true
|
||||
|
||||
# Check for common errors
|
||||
if grep -i "error" /tmp/docker-build.log; then
|
||||
echo "❌ Dockerfile validation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Dockerfile syntax valid"
|
||||
|
||||
- name: 🔍 Lint Shell Scripts
|
||||
run: |
|
||||
# Check shell script syntax
|
||||
for script in docker-entrypoint.sh tools/* integration-check.sh; do
|
||||
echo "Checking shell script syntax..."
|
||||
|
||||
# Check entrypoint
|
||||
if [ -f docker-entrypoint.sh ]; then
|
||||
bash -n docker-entrypoint.sh || exit 1
|
||||
echo "✅ docker-entrypoint.sh syntax valid"
|
||||
fi
|
||||
|
||||
# Check all tools
|
||||
for script in tools/*; do
|
||||
if [ -f "$script" ]; then
|
||||
# Check if it's a shell script
|
||||
if head -1 "$script" | grep -q "^#!/"; then
|
||||
sh -n "$script" || exit 1
|
||||
echo "✅ $script syntax valid"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Check additional scripts
|
||||
for script in integration-check.sh relay-status.sh; do
|
||||
if [ -f "$script" ]; then
|
||||
bash -n "$script" || exit 1
|
||||
echo "✓ $script syntax valid"
|
||||
echo "✅ $script syntax valid"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: 🔍 Install ShellCheck
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y shellcheck
|
||||
|
||||
- name: 🔍 Run ShellCheck on Scripts
|
||||
run: |
|
||||
echo "Running ShellCheck on shell scripts..."
|
||||
|
||||
# Check entrypoint with specific exclusions
|
||||
if [ -f docker-entrypoint.sh ]; then
|
||||
shellcheck -S warning docker-entrypoint.sh || true
|
||||
fi
|
||||
|
||||
# Check tools directory
|
||||
find tools -type f -exec shellcheck -S warning {} \; || true
|
||||
|
||||
echo "✅ ShellCheck analysis complete"
|
||||
|
||||
- name: 🔍 Lint YAML Files
|
||||
run: |
|
||||
# Install yq if not available
|
||||
if ! command -v yq &> /dev/null; then
|
||||
curl -sL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -o yq
|
||||
chmod +x yq
|
||||
export PATH=$PWD:$PATH
|
||||
fi
|
||||
|
||||
# Check YAML syntax
|
||||
for yaml in templates/*.yml docs/*.yml .github/workflows/*.yml; do
|
||||
echo "Installing yamllint..."
|
||||
pip install yamllint
|
||||
|
||||
echo "Validating YAML files..."
|
||||
for yaml in templates/*.yml docs/*.yml .github/workflows/*.yml .github/dependabot.yml; do
|
||||
if [ -f "$yaml" ]; then
|
||||
yq eval '.' "$yaml" > /dev/null || exit 1
|
||||
echo "✓ $yaml syntax valid"
|
||||
yamllint -d relaxed "$yaml" || exit 1
|
||||
echo "✅ $yaml syntax valid"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: 🔍 Validate JSON Files
|
||||
run: |
|
||||
# Check JSON syntax
|
||||
for json in templates/*.json examples/*.json; do
|
||||
echo "Validating JSON files..."
|
||||
for json in templates/*.json examples/*.json .github/*.json; do
|
||||
if [ -f "$json" ]; then
|
||||
python3 -m json.tool "$json" > /dev/null || exit 1
|
||||
echo "✓ $json syntax valid"
|
||||
echo "✅ $json syntax valid"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: 📋 Check Documentation
|
||||
run: |
|
||||
# Verify key documentation files exist
|
||||
echo "Verifying required documentation files..."
|
||||
required_docs=(
|
||||
"README.md"
|
||||
"docs/DEPLOYMENT.md"
|
||||
"docs/BACKUP.md"
|
||||
"docs/PERFORMANCE.md"
|
||||
"docs/LEGAL.md"
|
||||
"CHANGELOG.md"
|
||||
"SECURITY.md"
|
||||
"CONTRIBUTING.md"
|
||||
"CODE_OF_CONDUCT.md"
|
||||
"LICENSE.txt"
|
||||
)
|
||||
|
||||
for doc in "${required_docs[@]}"; do
|
||||
@@ -90,8 +136,20 @@ jobs:
|
||||
echo "❌ Missing documentation: $doc"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ $doc exists"
|
||||
echo "✅ $doc exists"
|
||||
done
|
||||
|
||||
# Check docs directory structure
|
||||
if [ -d "docs" ]; then
|
||||
echo "📁 Checking docs/ directory..."
|
||||
for doc in DEPLOYMENT.md BACKUP.md PERFORMANCE.md LEGAL.md MONITORING.md TOOLS.md; do
|
||||
if [ -f "docs/$doc" ]; then
|
||||
echo "✅ docs/$doc exists"
|
||||
else
|
||||
echo "⚠️ docs/$doc missing (optional)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
- name: ✅ Lint Summary
|
||||
run: echo "✅ All linting checks passed"
|
||||
@@ -103,7 +161,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: ⚙️ Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@@ -114,7 +172,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 🐳 Build Image (amd64 for testing)
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -124,6 +182,8 @@ jobs:
|
||||
build-args: |
|
||||
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
BUILD_VERSION=test-${{ github.run_number }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: 💾 Save Image for Testing
|
||||
run: |
|
||||
@@ -131,7 +191,7 @@ jobs:
|
||||
echo "Image size: $(du -h /tmp/tor-relay-test.tar | cut -f1)"
|
||||
|
||||
- name: 📤 Upload Image Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
path: /tmp/tor-relay-test.tar
|
||||
@@ -144,10 +204,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: 📥 Download Docker Image
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
path: /tmp
|
||||
@@ -157,53 +217,82 @@ jobs:
|
||||
docker load -i /tmp/tor-relay-test.tar
|
||||
docker image ls tor-relay
|
||||
|
||||
- name: 🧪 Run Container
|
||||
- name: 🧪 Run Container Smoke Test
|
||||
run: |
|
||||
# Start container in background (without actual Tor relay, just test setup)
|
||||
echo "Starting container for smoke test..."
|
||||
|
||||
# Start container in background (test mode)
|
||||
docker run -d \
|
||||
--name tor-test \
|
||||
--network host \
|
||||
-e ENABLE_METRICS=true \
|
||||
-e ENABLE_HEALTH_CHECK=true \
|
||||
-v /tmp/torrc:/etc/tor/torrc:ro \
|
||||
tor-relay:test \
|
||||
/bin/sh -c "echo 'Test mode'; sleep 30" || true
|
||||
/bin/sh -c "echo 'Test mode active'; sleep 60" || true
|
||||
|
||||
# Wait for container to start
|
||||
sleep 5
|
||||
|
||||
- name: ✅ Check Container Status
|
||||
run: |
|
||||
echo "Container status:"
|
||||
docker ps -a | grep tor-test || true
|
||||
docker logs tor-test 2>&1 | head -20 || true
|
||||
|
||||
echo ""
|
||||
echo "Container logs:"
|
||||
docker logs tor-test 2>&1 | head -30 || true
|
||||
|
||||
- name: 🧩 Run Integration Check
|
||||
- name: 🧩 Verify Tools Installation
|
||||
run: |
|
||||
# Check tool availability inside image
|
||||
docker run --rm \
|
||||
-v $(pwd):/workspace:ro \
|
||||
tor-relay:test \
|
||||
ls -la /usr/local/bin/ || exit 1
|
||||
echo "Checking installed tools..."
|
||||
docker run --rm tor-relay:test ls -la /usr/local/bin/ || exit 1
|
||||
|
||||
echo ""
|
||||
echo "Available diagnostic tools:"
|
||||
docker run --rm tor-relay:test sh -c "ls -1 /usr/local/bin/ | grep -v '^tor$' | sort"
|
||||
|
||||
# List available tools
|
||||
docker run --rm tor-relay:test sh -c "ls -1 /usr/local/bin/ | grep -v '^tor$'" || true
|
||||
- name: 🧅 Verify Tor Installation
|
||||
run: |
|
||||
echo "Checking Tor version..."
|
||||
TOR_VERSION=$(docker run --rm tor-relay:test tor --version | head -1)
|
||||
|
||||
if [ -z "$TOR_VERSION" ]; then
|
||||
echo "❌ Tor installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Tor installed: $TOR_VERSION"
|
||||
|
||||
- name: 📋 Verify File Structure
|
||||
run: |
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
echo '=== Directory Structure ===' && \
|
||||
ls -la / | grep -E 'var|etc|usr' && \
|
||||
echo '' && \
|
||||
echo '=== Tools Directory ===' && \
|
||||
ls -la /usr/local/bin/ && \
|
||||
echo '' && \
|
||||
echo '=== Data Directory ===' && \
|
||||
ls -la /var/lib/tor 2>/dev/null || echo '(data dir empty, expected)' && \
|
||||
echo '' && \
|
||||
echo '=== Log Directory ===' && \
|
||||
ls -la /var/log/tor 2>/dev/null || echo '(log dir empty, expected)'
|
||||
"
|
||||
|
||||
- name: 🏥 Check Build Metadata
|
||||
run: |
|
||||
echo "Checking build metadata..."
|
||||
docker run --rm tor-relay:test cat /build-info.txt || exit 1
|
||||
|
||||
- name: 🔒 Verify Permissions
|
||||
run: |
|
||||
echo "Verifying directory permissions..."
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
ls -ld /var/lib/tor | grep -q '^d.*tor tor' && echo '✅ /var/lib/tor permissions correct' || exit 1
|
||||
ls -ld /var/log/tor | grep -q '^d.*tor tor' && echo '✅ /var/log/tor permissions correct' || exit 1
|
||||
"
|
||||
|
||||
- name: ✅ Integration Tests Summary
|
||||
run: echo "✅ Integration tests completed"
|
||||
run: echo "✅ Integration tests completed successfully"
|
||||
|
||||
- name: 🧹 Cleanup
|
||||
if: always()
|
||||
@@ -218,10 +307,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: 📥 Download Docker Image
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
path: /tmp
|
||||
@@ -238,11 +327,18 @@ jobs:
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: 📤 Upload Trivy Results
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
continue-on-error: true
|
||||
|
||||
- name: 🔒 Trivy Vulnerability Report
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: 'tor-relay:test'
|
||||
format: 'table'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: ✅ Security Scan Complete
|
||||
run: echo "✅ Security scan completed"
|
||||
|
||||
@@ -252,18 +348,20 @@ jobs:
|
||||
needs: build
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test-case:
|
||||
- 'help-flags'
|
||||
- 'file-permissions'
|
||||
- 'alpine-compatibility'
|
||||
- 'tool-executability'
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: 📥 Download Docker Image
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
path: /tmp
|
||||
@@ -272,33 +370,63 @@ jobs:
|
||||
run: docker load -i /tmp/tor-relay-test.tar
|
||||
|
||||
- name: "🧪 Test: Help Flags"
|
||||
if: "matrix.test-case == 'help-flags'"
|
||||
if: matrix.test-case == 'help-flags'
|
||||
run: |
|
||||
# Test that tools respond to --help
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
/usr/local/bin/health --help > /dev/null && echo '✓ health --help' || true
|
||||
/usr/local/bin/metrics --help > /dev/null && echo '✓ metrics --help' || true
|
||||
/usr/local/bin/dashboard --help > /dev/null && echo '✓ dashboard --help' || true
|
||||
" || true
|
||||
echo "Testing tool help flags..."
|
||||
|
||||
for tool in health metrics status fingerprint view-logs net-check setup dashboard; do
|
||||
if docker run --rm tor-relay:test sh -c "command -v $tool" > /dev/null 2>&1; then
|
||||
echo "Testing $tool --help..."
|
||||
docker run --rm tor-relay:test $tool --help > /dev/null && echo "✅ $tool --help works" || echo "⚠️ $tool --help failed"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: "🧪 Test: File Permissions"
|
||||
if: "matrix.test-case == 'file-permissions'"
|
||||
if: matrix.test-case == 'file-permissions'
|
||||
run: |
|
||||
# Verify executable permissions
|
||||
echo "Verifying file permissions..."
|
||||
|
||||
# Check tool executability
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
test -x /usr/local/bin/health && echo '✓ health is executable' || exit 1
|
||||
test -x /usr/local/bin/metrics && echo '✓ metrics is executable' || exit 1
|
||||
test -x /usr/local/bin/dashboard && echo '✓ dashboard is executable' || exit 1
|
||||
for tool in health metrics status fingerprint view-logs net-check setup dashboard; do
|
||||
if command -v \$tool > /dev/null 2>&1; then
|
||||
test -x /usr/local/bin/\$tool && echo \"✅ \$tool is executable\" || exit 1
|
||||
fi
|
||||
done
|
||||
"
|
||||
|
||||
# Check entrypoint
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
test -x /usr/local/bin/docker-entrypoint.sh && echo '✅ entrypoint is executable' || exit 1
|
||||
"
|
||||
|
||||
- name: "🧪 Test: Alpine Compatibility"
|
||||
if: "matrix.test-case == 'alpine-compatibility'"
|
||||
if: matrix.test-case == 'alpine-compatibility'
|
||||
run: |
|
||||
# Verify Alpine base image
|
||||
echo "Verifying Alpine base image..."
|
||||
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
cat /etc/os-release | grep -i alpine && echo '✓ Alpine base confirmed' || exit 1
|
||||
test -x /bin/sh && echo '✓ /bin/sh available' || exit 1
|
||||
apk --version > /dev/null 2>&1 && echo '✓ apk available' || exit 1
|
||||
cat /etc/os-release | grep -i alpine && echo '✅ Alpine base confirmed' || exit 1
|
||||
test -x /bin/sh && echo '✅ /bin/sh available' || exit 1
|
||||
apk --version > /dev/null 2>&1 && echo '✅ apk available' || exit 1
|
||||
command -v tor > /dev/null 2>&1 && echo '✅ tor available' || exit 1
|
||||
"
|
||||
|
||||
- name: "🧪 Test: Tool Executability"
|
||||
if: matrix.test-case == 'tool-executability'
|
||||
run: |
|
||||
echo "Testing tool execution..."
|
||||
|
||||
# Test each tool can execute without errors
|
||||
docker run --rm tor-relay:test sh -c "
|
||||
# Test health (should work even without Tor running)
|
||||
health --json > /dev/null 2>&1 || echo 'health requires Tor process'
|
||||
|
||||
# Test metrics
|
||||
metrics --help > /dev/null 2>&1 && echo '✅ metrics executable' || exit 1
|
||||
|
||||
# Test status
|
||||
status --help > /dev/null 2>&1 && echo '✅ status executable' || exit 1
|
||||
"
|
||||
|
||||
summary:
|
||||
@@ -314,43 +442,46 @@ jobs:
|
||||
# 🧱 Build & Validation Pipeline
|
||||
|
||||
## Pipeline Status
|
||||
- Lint: ${{ needs.lint.result }}
|
||||
- Build: ${{ needs.build.result }}
|
||||
- Integration: ${{ needs.integration.result }}
|
||||
- Security: ${{ needs.security.result }}
|
||||
- Test Matrix: ${{ needs.test-matrix.result }}
|
||||
- **Lint:** \`${{ needs.lint.result }}\`
|
||||
- **Build:** \`${{ needs.build.result }}\`
|
||||
- **Integration:** \`${{ needs.integration.result }}\`
|
||||
- **Security:** \`${{ needs.security.result }}\`
|
||||
- **Test Matrix:** \`${{ needs.test-matrix.result }}\`
|
||||
|
||||
## Build Information
|
||||
- **Branch:** `${{ github.ref }}`
|
||||
- **Commit:** `${{ github.sha }}`
|
||||
- **Run Number:** `${{ github.run_number }}`
|
||||
- **Date:** `$(date -u +'%Y-%m-%dT%H:%M:%SZ')`
|
||||
- **Branch:** \`${{ github.ref }}\`
|
||||
- **Commit:** \`${{ github.sha }}\`
|
||||
- **Run Number:** \`${{ github.run_number }}\`
|
||||
- **Date:** \`$(date -u +'%Y-%m-%dT%H:%M:%SZ')\`
|
||||
|
||||
## Checks Performed
|
||||
- ✅ Dockerfile syntax validation
|
||||
- ✅ Shell script linting
|
||||
- ✅ Dockerfile validation with Hadolint
|
||||
- ✅ Shell script syntax checking
|
||||
- ✅ ShellCheck analysis
|
||||
- ✅ YAML validation
|
||||
- ✅ JSON validation
|
||||
- ✅ Documentation verification
|
||||
- ✅ Docker image build
|
||||
- ✅ Container startup test
|
||||
- ✅ Docker image build (multi-arch ready)
|
||||
- ✅ Container smoke test
|
||||
- ✅ Tor installation verification
|
||||
- ✅ Tool availability check
|
||||
- ✅ Security scanning
|
||||
- ✅ File permissions check
|
||||
- ✅ Alpine compatibility check
|
||||
- ✅ Permission verification
|
||||
- ✅ Security scanning with Trivy
|
||||
- ✅ Test matrix execution
|
||||
|
||||
## Next Steps
|
||||
If all checks pass:
|
||||
- Image is ready for testing
|
||||
- Image is ready for production use
|
||||
- Release workflow will handle publishing to GHCR
|
||||
- Tag with semantic version to trigger release
|
||||
EOF
|
||||
|
||||
- name: ✅ Build Pipeline Complete
|
||||
if: success()
|
||||
run: echo "✅ All build checks passed successfully"
|
||||
run: echo "✅ All build and validation checks passed successfully"
|
||||
|
||||
- name: ❌ Build Pipeline Failed
|
||||
if: failure()
|
||||
run: |
|
||||
echo "❌ Build pipeline failed"
|
||||
echo "❌ Build pipeline failed - check logs above for details"
|
||||
exit 1
|
||||
70
CHANGELOG.md
70
CHANGELOG.md
@@ -9,8 +9,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Planned Features
|
||||
- Bridge relay variant
|
||||
- Additional monitoring integrations
|
||||
|
||||
## [1.1.1] - 2025-11-05
|
||||
## [1.0.2] - 2025-11-05
|
||||
|
||||
### 🔒 Security Hardening
|
||||
|
||||
**Network Exposure Model**
|
||||
- Enforced strict two-port exposure policy (9001 ORPort, 9030 DirPort only)
|
||||
- All monitoring services (metrics, health, dashboard) bound to localhost (127.0.0.1)
|
||||
- Updated documentation to reflect secure-by-default configuration
|
||||
|
||||
**Tool Security**
|
||||
- Changed default bind addresses from 0.0.0.0 → 127.0.0.1 (dashboard, metrics-http)
|
||||
- Added automatic configuration backup in setup tool
|
||||
- Implemented rate limiting for HTTP servers
|
||||
- Enhanced SIGTERM handling for clean shutdowns
|
||||
|
||||
**Infrastructure**
|
||||
- Pinned base image to Alpine 3.22.2 for reproducible builds
|
||||
- Updated GitHub Actions to latest versions
|
||||
- Fixed invalid docker build commands in CI/CD
|
||||
- Enhanced process cleanup in docker-entrypoint.sh
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
**New Content**
|
||||
- Comprehensive port exposure policy documentation
|
||||
- Security model clarification (public vs internal ports)
|
||||
- Enhanced deployment examples with security warnings
|
||||
- Improved monitoring setup guides with localhost binding examples
|
||||
|
||||
**Updates**
|
||||
- All documentation aligned to v1.0.2
|
||||
- Corrected version references throughout
|
||||
- Enhanced security warnings for external port exposure
|
||||
- Updated template configurations
|
||||
|
||||
### 🛠️ Technical Improvements
|
||||
|
||||
**Scripts**
|
||||
- Enhanced integration-check.sh with port validation
|
||||
- Improved relay-status.sh output formatting
|
||||
- Added version consistency checks
|
||||
|
||||
**Templates**
|
||||
- Updated all docker-compose templates with explicit port policies
|
||||
- Enhanced Prometheus/Grafana configurations
|
||||
- Improved Cosmos Cloud templates with security annotations
|
||||
|
||||
### 🐛 Fixes
|
||||
- Corrected version inconsistencies across documentation
|
||||
- Fixed port exposure examples in deployment guides
|
||||
- Updated monitoring endpoint documentation
|
||||
|
||||
---
|
||||
|
||||
## [1.0.1] - 2025-11-05
|
||||
|
||||
### 🎉 Major Restructuring
|
||||
|
||||
@@ -39,6 +94,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Same tool interfaces
|
||||
- Volume mounts unchanged
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2025-11-01
|
||||
|
||||
@@ -119,6 +175,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## Release Information
|
||||
|
||||
- **First Release:** v1.0 (November 1, 2025)
|
||||
- **Current Release:** v1.0.2
|
||||
- **Latest Release:** [GitHub Releases](https://github.com/r3bo0tbx1/tor-guard-relay/releases/latest)
|
||||
- **Docker Images:** [GHCR Package](https://github.com/r3bo0tbx1/tor-guard-relay/pkgs/container/onion-relay)
|
||||
|
||||
@@ -126,8 +183,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
| Version | Status | Support Period |
|
||||
|---------|--------|----------------|
|
||||
| 1.0.x | ✅ Actively Supported | Current |
|
||||
| Future versions | - | TBD |
|
||||
| 1.0.2 | ✅ Actively Supported | Current |
|
||||
| 1.0.1 | ✅ Supported | Until v1.1.0 |
|
||||
| 1.0.0 | ⚠️ Legacy | Security updates only |
|
||||
|
||||
[1.0]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.0
|
||||
[Unreleased]: https://github.com/r3bo0tbx1/tor-guard-relay/compare/v1.0...HEAD
|
||||
[1.0.2]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.0.2
|
||||
[1.0.1]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.0.1
|
||||
[1.0.0]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.0.0
|
||||
[Unreleased]: https://github.com/r3bo0tbx1/tor-guard-relay/compare/v1.0.2...HEAD
|
||||
66
Dockerfile
66
Dockerfile
@@ -1,12 +1,17 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
FROM alpine:latest
|
||||
# ============================================================================
|
||||
# Tor Guard Relay - Hardened relay with diagnostics and auto-healing
|
||||
# Base: Alpine 3.22.2 | Multi-arch: amd64, arm64
|
||||
# ============================================================================
|
||||
|
||||
# Metadata Arguments
|
||||
FROM alpine:3.22.2
|
||||
|
||||
# Build arguments
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG TARGETARCH
|
||||
|
||||
# Labels
|
||||
# OCI labels
|
||||
LABEL maintainer="rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>" \
|
||||
org.opencontainers.image.title="Tor Guard Relay" \
|
||||
org.opencontainers.image.description="🧅 Hardened Tor Guard Relay with diagnostics & auto-healing" \
|
||||
@@ -15,16 +20,16 @@ LABEL maintainer="rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>" \
|
||||
org.opencontainers.image.source="https://github.com/r3bo0tbx1/tor-guard-relay" \
|
||||
org.opencontainers.image.documentation="https://github.com/r3bo0tbx1/tor-guard-relay#readme" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.vendor="r3bo0tbx1" \
|
||||
org.opencontainers.image.authors="rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>" \
|
||||
org.opencontainers.image.url="https://github.com/r3bo0tbx1/tor-guard-relay" \
|
||||
org.opencontainers.image.base.name="docker.io/library/alpine:3.22.2" \
|
||||
org.opencontainers.image.revision="${TARGETARCH}"
|
||||
|
||||
# Core dependencies
|
||||
# - tor: main service
|
||||
# - bash: entrypoint logic
|
||||
# - tini: init process manager
|
||||
# - curl: for diagnostics and network checks
|
||||
# - jq, grep, coreutils: JSON parsing & utils
|
||||
# - bind-tools: provides nslookup/dig for DNS diagnostics
|
||||
# - netcat-openbsd: for port checking in net-check
|
||||
# Install core dependencies
|
||||
# tor: relay daemon | bash: entrypoint | tini: init system (PID 1)
|
||||
# curl: diagnostics | jq: JSON parsing | bind-tools: DNS (dig/nslookup)
|
||||
# netcat-openbsd: port checking | coreutils/grep: utilities
|
||||
RUN apk add --no-cache \
|
||||
tor \
|
||||
bash \
|
||||
@@ -37,24 +42,27 @@ RUN apk add --no-cache \
|
||||
netcat-openbsd && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
# Directory structure setup
|
||||
# Setup directories with proper permissions
|
||||
# /var/lib/tor: data (keys, state) - 700 for security
|
||||
# /var/log/tor: logs - 755 for diagnostics
|
||||
# /run/tor: runtime (PID, socket) - 755 for health checks
|
||||
RUN mkdir -p /var/lib/tor /var/log/tor /run/tor && \
|
||||
chown -R tor:tor /var/lib/tor /var/log/tor /run/tor && \
|
||||
chmod 700 /var/lib/tor && \
|
||||
chmod 755 /var/log/tor /run/tor
|
||||
|
||||
# Default configuration placeholder
|
||||
# Default configuration placeholder (mount your own at runtime)
|
||||
RUN echo "# 🧅 Tor configuration is mounted at runtime" > /etc/tor/torrc
|
||||
|
||||
# Build metadata
|
||||
# Embed build metadata
|
||||
RUN printf "Version: %s\nBuild Date: %s\nArchitecture: %s\n" \
|
||||
"${BUILD_VERSION:-dev}" "${BUILD_DATE:-unknown}" "${TARGETARCH:-amd64}" > /build-info.txt
|
||||
|
||||
# Copy entrypoint and diagnostic tools
|
||||
# Copy application files
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
COPY tools/ /usr/local/bin/
|
||||
|
||||
# Normalize scripts: remove CRLFs, BOMs, and fix permissions
|
||||
# Normalize scripts: remove CRLF, BOM, and set permissions
|
||||
RUN set -eux; \
|
||||
for f in /usr/local/bin/*; do \
|
||||
[ -f "$f" ] || continue; \
|
||||
@@ -63,35 +71,39 @@ RUN set -eux; \
|
||||
chmod +x "$f"; \
|
||||
done; \
|
||||
echo "🧩 Installed tools:"; \
|
||||
ls -1 /usr/local/bin | grep -E 'docker-entrypoint|net-check|metrics|health|view-logs' || true
|
||||
ls -1 /usr/local/bin | grep -E 'docker-entrypoint|net-check|metrics|health|view-logs|status|fingerprint|setup|dashboard' || true
|
||||
|
||||
# Environment defaults
|
||||
# Environment configuration
|
||||
ENV TOR_DATA_DIR=/var/lib/tor \
|
||||
TOR_LOG_DIR=/var/log/tor \
|
||||
TOR_CONFIG=/etc/tor/torrc \
|
||||
ENABLE_METRICS=false \
|
||||
ENABLE_HEALTH_CHECK=true \
|
||||
ENABLE_NET_CHECK=true \
|
||||
ENABLE_NET_CHECK=false \
|
||||
PATH="/usr/local/bin:$PATH"
|
||||
|
||||
# Cleanup
|
||||
# Cleanup to minimize image size
|
||||
RUN rm -rf /usr/share/man /tmp/* /var/tmp/* /root/.cache/*
|
||||
|
||||
# Runtime permissions (non-root safe)
|
||||
# Ensure runtime directory writable by tor user
|
||||
RUN mkdir -p /run/tor && \
|
||||
chown -R tor:tor /run/tor && \
|
||||
chmod 770 /run/tor
|
||||
|
||||
# Non-root execution
|
||||
# Switch to non-root user
|
||||
USER tor
|
||||
|
||||
# Expose relay + diagnostics ports
|
||||
EXPOSE 9001 9035 9036
|
||||
# Expose network ports
|
||||
# 9001: ORPort - Tor relay traffic (REQUIRED, PUBLIC)
|
||||
# 9030: DirPort - Directory service (OPTIONAL, PUBLIC)
|
||||
# 9035: Metrics - Prometheus endpoint (OPTIONAL, localhost recommended)
|
||||
# 9036: Health - Status endpoint (OPTIONAL, localhost recommended)
|
||||
EXPOSE 9001 9030
|
||||
|
||||
# Healthcheck - ensures Tor config remains valid
|
||||
# Health check - validates Tor config every 10 minutes
|
||||
HEALTHCHECK --interval=10m --timeout=15s --start-period=30s --retries=3 \
|
||||
CMD tor --verify-config -f "$TOR_CONFIG" || exit 1
|
||||
|
||||
# Entrypoint through tini
|
||||
# Use tini for signal handling, entrypoint for initialization
|
||||
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["tor", "-f", "/etc/tor/torrc"]
|
||||
CMD ["tor", "-f", "/etc/tor/torrc"]
|
||||
30
README.md
30
README.md
@@ -37,6 +37,24 @@
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Model
|
||||
|
||||
**Port Exposure Policy:**
|
||||
- **9001** (ORPort) - Tor relay traffic - **PUBLIC**
|
||||
- **9030** (DirPort) - Directory service - **PUBLIC**
|
||||
- **9035** (Metrics) - Prometheus endpoint - **LOCALHOST ONLY** (127.0.0.1)
|
||||
- **9036** (Health) - Health check API - **LOCALHOST ONLY** (127.0.0.1)
|
||||
|
||||
All monitoring and diagnostic services are bound to `127.0.0.1` by default. To expose externally:
|
||||
```bash
|
||||
# ⚠️ INSECURE - Only for testing/development
|
||||
docker run -e DASHBOARD_BIND=0.0.0.0 ...
|
||||
```
|
||||
|
||||
**Production recommendation**: Use reverse proxy with authentication for external monitoring access.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Start
|
||||
|
||||
### System Requirements
|
||||
@@ -52,10 +70,16 @@
|
||||
|
||||
**Supported Architectures:** AMD64 (x86_64) • ARM64 (aarch64)
|
||||
|
||||
### Network Security Notice
|
||||
|
||||
⚠️ **Port Exposure:**
|
||||
- Only ports **9001** (ORPort) and **9030** (DirPort) should be publicly accessible
|
||||
- Monitoring services (9035, 9036) are **localhost-only** by default
|
||||
- Use `--network host` or explicit port mapping: `-p 9001:9001 -p 9030:9030`
|
||||
|
||||
### Deploy in 30 Seconds
|
||||
|
||||
**Step 1:** Create your relay configuration (or use our [example](examples/relay.conf)):
|
||||
|
||||
```bash
|
||||
# Create config directory
|
||||
mkdir -p ~/tor-relay && cd ~/tor-relay
|
||||
@@ -69,7 +93,6 @@ nano relay.conf
|
||||
```
|
||||
|
||||
**Step 2:** Run the relay:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name tor-relay \
|
||||
@@ -82,7 +105,6 @@ docker run -d \
|
||||
```
|
||||
|
||||
**Step 3:** Verify it's running:
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
docker exec tor-relay status
|
||||
@@ -513,7 +535,7 @@ Images are automatically rebuilt weekly to include security patches:
|
||||

|
||||

|
||||
|
||||
**Current Version:** v1.1
|
||||
**Current Version:** v1.0.2
|
||||
**Status:** Production Ready
|
||||
**Last Build:** Weekly (Mondays 03:00 UTC)
|
||||
|
||||
|
||||
460
SECURITY.md
460
SECURITY.md
@@ -14,26 +14,248 @@ We actively support the following versions with security updates:
|
||||
|
||||
| Version | Supported | Status |
|
||||
|----------|------------|--------|
|
||||
| 1.0 | ✅ | Current stable release |
|
||||
| 1.0.2 | ✅ | Current stable release |
|
||||
| 1.0.1 | ✅ | Maintenance support |
|
||||
| 1.0.0 | ⚠️ | Security patches only |
|
||||
| < 1.0 | ❌ | Pre-release versions |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Network Security Model
|
||||
|
||||
### External Port Exposure
|
||||
|
||||
**CRITICAL: Only TWO ports should be exposed to the public internet:**
|
||||
|
||||
```
|
||||
EXPOSED PORTS (Public):
|
||||
9001/tcp → Tor ORPort (Onion Router Port)
|
||||
9030/tcp → Tor DirPort (Directory Service Port)
|
||||
```
|
||||
|
||||
**All other services MUST bind to localhost only (127.0.0.1)** for security:
|
||||
|
||||
```
|
||||
INTERNAL SERVICES (Localhost Only):
|
||||
127.0.0.1:9035 → Prometheus metrics endpoint
|
||||
127.0.0.1:9036 → Health check API
|
||||
127.0.0.1:9037 → Dashboard HTTP server
|
||||
127.0.0.1:9038+ → Additional relay instances
|
||||
```
|
||||
|
||||
### Network Architecture
|
||||
|
||||
This project follows a **two-tier port exposure model**:
|
||||
|
||||
#### 🌐 Public Ports (External Access Required)
|
||||
|
||||
- **9001** - ORPort (Tor relay traffic)
|
||||
- Must be publicly accessible
|
||||
- Firewall: Allow inbound TCP
|
||||
- Required for relay operation
|
||||
|
||||
- **9030** - DirPort (Directory service)
|
||||
- Should be publicly accessible
|
||||
- Optional but recommended
|
||||
- Reduces load on directory authorities
|
||||
|
||||
#### 🔒 Internal Ports (Localhost Only)
|
||||
|
||||
- **9035** - Metrics HTTP (Prometheus endpoint)
|
||||
- Bound to 127.0.0.1 by default
|
||||
- Access via: `docker exec` or localhost tunnel
|
||||
- ⚠️ Never expose without authentication
|
||||
|
||||
- **9036** - Health Check (Status API)
|
||||
- Bound to 127.0.0.1 by default
|
||||
- For monitoring systems only
|
||||
- ⚠️ Never expose without authentication
|
||||
|
||||
- **9037** - Dashboard HTTP (Web UI)
|
||||
- Bound to 127.0.0.1 by default
|
||||
- Access via: SSH tunnel or reverse proxy
|
||||
- ⚠️ Never expose without authentication
|
||||
|
||||
### Port Policy Rationale
|
||||
|
||||
**Why this matters:**
|
||||
- ✅ **Minimizes attack surface** - Only Tor protocol ports exposed
|
||||
- ✅ **Prevents unauthorized access** - Metrics/dashboards remain private
|
||||
- ✅ **Follows Tor best practices** - Standard relay configuration
|
||||
- ✅ **Defense in depth** - Additional layer beyond firewall rules
|
||||
|
||||
**Security implications:**
|
||||
- Exposed ORPort (9001): Required for Tor relay operation
|
||||
- Exposed DirPort (9030): Optional but recommended for directory service
|
||||
- Internal metrics (9035+): Protected from external access
|
||||
- No other services should be externally accessible
|
||||
|
||||
### Port Exposure Best Practices
|
||||
|
||||
#### ✅ Secure Configuration
|
||||
|
||||
```bash
|
||||
# Docker CLI - Secure port mapping
|
||||
docker run -d \
|
||||
--name tor-relay \
|
||||
-p 9001:9001 \
|
||||
-p 9030:9030 \
|
||||
-p 127.0.0.1:9035:9035 \
|
||||
-p 127.0.0.1:9036:9036 \
|
||||
ghcr.io/r3bo0tbx1/onion-relay:latest
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Docker Compose - Secure port mapping
|
||||
services:
|
||||
tor-relay:
|
||||
ports:
|
||||
- "9001:9001" # Public - ORPort
|
||||
- "9030:9030" # Public - DirPort
|
||||
- "127.0.0.1:9035:9035" # Localhost - Metrics
|
||||
- "127.0.0.1:9036:9036" # Localhost - Health
|
||||
```
|
||||
|
||||
#### ❌ Insecure Configuration (DO NOT USE)
|
||||
|
||||
```bash
|
||||
# ❌ BAD - Exposes monitoring without auth
|
||||
docker run -p 0.0.0.0:9035:9035 ...
|
||||
|
||||
# ❌ BAD - Exposes all internal services
|
||||
docker run -p 9035:9035 -p 9036:9036 ...
|
||||
|
||||
# ⚠️ USE WITH CAUTION - Host network mode
|
||||
docker run --network host ...
|
||||
# Only use if you understand implications and have proper firewall rules
|
||||
```
|
||||
|
||||
### External Monitoring Access
|
||||
|
||||
If you need external access to metrics/health endpoints, use one of these secure methods:
|
||||
|
||||
#### Option 1: Reverse Proxy with Authentication (Recommended)
|
||||
|
||||
```nginx
|
||||
# Nginx with HTTP Basic Auth
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name metrics.example.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location /metrics {
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
proxy_pass http://127.0.0.1:9035;
|
||||
}
|
||||
|
||||
location /health {
|
||||
auth_basic "Restricted";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
proxy_pass http://127.0.0.1:9036;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Option 2: SSH Tunnel
|
||||
|
||||
```bash
|
||||
# Forward remote metrics to local machine
|
||||
ssh -L 9035:localhost:9035 user@relay-server
|
||||
ssh -L 9036:localhost:9036 user@relay-server
|
||||
|
||||
# Access locally (no public exposure)
|
||||
curl http://localhost:9035/metrics
|
||||
curl http://localhost:9036/health
|
||||
```
|
||||
|
||||
#### Option 3: VPN Access
|
||||
|
||||
- Deploy relay and monitoring in same VPN
|
||||
- Access internal ports over encrypted VPN tunnel
|
||||
- No public exposure required
|
||||
- Recommended for multi-relay deployments
|
||||
|
||||
### Default Behavior Changes (v1.0.2)
|
||||
|
||||
Prior to v1.0.2, some tools defaulted to `0.0.0.0` (all interfaces). **As of v1.0.2:**
|
||||
|
||||
| Tool | Previous Default | New Default | Override |
|
||||
|------|------------------|-------------|----------|
|
||||
| dashboard | 0.0.0.0:8080 | 127.0.0.1:8080 | `DASHBOARD_BIND=0.0.0.0` |
|
||||
| metrics-http | 0.0.0.0:9035 | 127.0.0.1:9035 | `METRICS_BIND=0.0.0.0` |
|
||||
|
||||
**Migration Note**: If you previously relied on external access without explicit configuration, you must now:
|
||||
|
||||
1. **Use environment variables** to bind to `0.0.0.0` (NOT recommended), OR
|
||||
2. **Implement reverse proxy** with authentication (RECOMMENDED)
|
||||
|
||||
Example of override (only if you understand the security implications):
|
||||
|
||||
```bash
|
||||
# NOT RECOMMENDED - Only use in trusted networks
|
||||
docker run -d \
|
||||
-e METRICS_BIND=0.0.0.0 \
|
||||
-p 9035:9035 \
|
||||
ghcr.io/r3bo0tbx1/onion-relay:latest
|
||||
```
|
||||
|
||||
### Firewall Configuration
|
||||
|
||||
**Recommended firewall rules:**
|
||||
|
||||
```bash
|
||||
# UFW (Ubuntu/Debian)
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw allow 9001/tcp # ORPort
|
||||
sudo ufw allow 9030/tcp # DirPort (optional)
|
||||
sudo ufw enable
|
||||
|
||||
# iptables
|
||||
sudo iptables -A INPUT -p tcp --dport 9001 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 9030 -j ACCEPT
|
||||
sudo iptables -A INPUT -j DROP
|
||||
|
||||
# firewalld (RHEL/CentOS)
|
||||
sudo firewall-cmd --permanent --add-port=9001/tcp
|
||||
sudo firewall-cmd --permanent --add-port=9030/tcp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
### Docker Network Mode
|
||||
|
||||
**Host networking (network_mode: host):**
|
||||
- Used for dual-stack IPv4/IPv6 support
|
||||
- Allows direct port binding without NAT
|
||||
- Services still bind to 127.0.0.1 for internal access
|
||||
- Container remains isolated (non-root user, dropped capabilities)
|
||||
|
||||
**Security measures when using host networking:**
|
||||
- Non-root execution (runs as `tor` user)
|
||||
- Capability restrictions (`--cap-drop ALL`)
|
||||
- No new privileges (`--security-opt no-new-privileges:true`)
|
||||
- Minimal Alpine base image
|
||||
- Automatic permission healing
|
||||
|
||||
---
|
||||
|
||||
## Security Updates
|
||||
|
||||
- Security patches are released as soon as possible after discovery.
|
||||
- Critical vulnerabilities are prioritized.
|
||||
- Weekly automated builds include the latest Alpine and Tor security updates.
|
||||
- Subscribe to [GitHub Releases](https://github.com/r3bo0tbx1/tor-guard-relay/releases) for notifications.
|
||||
- Security patches are released as soon as possible after discovery
|
||||
- Critical vulnerabilities are prioritized
|
||||
- Weekly automated builds include the latest Alpine and Tor security updates
|
||||
- Subscribe to [GitHub Releases](https://github.com/r3bo0tbx1/tor-guard-relay/releases) for notifications
|
||||
|
||||
### Automated Hardening
|
||||
|
||||
To reduce patch-lag risk, GitHub Actions automatically:
|
||||
1. Pulls the latest Alpine base image.
|
||||
2. Installs the latest Tor package.
|
||||
3. Applies security patches.
|
||||
4. Rebuilds multi-architecture images.
|
||||
5. Publishes to GHCR.
|
||||
1. Pulls the latest Alpine base image
|
||||
2. Installs the latest Tor package
|
||||
3. Applies security patches
|
||||
4. Rebuilds multi-architecture images
|
||||
5. Publishes to GHCR
|
||||
|
||||
These rebuilds include Alpine CVE patches and Tor security fixes without changing functionality.
|
||||
|
||||
@@ -47,36 +269,40 @@ These rebuilds include Alpine CVE patches and Tor security fixes without changin
|
||||
|
||||
**Email:** r3bo0tbx1@brokenbotnet.com
|
||||
**Subject:** `[SECURITY] Tor Guard Relay – <short summary>`
|
||||
Please use my PGP [0xB3BD6196E1CFBFB4 🔑](https://keys.openpgp.org/vks/v1/by-fingerprint/33727F5377D296C320AF704AB3BD6196E1CFBFB4) to encrypt if your report contains sensitive technical details.
|
||||
|
||||
Please use my PGP key [0xB3BD6196E1CFBFB4 🔑](https://keys.openpgp.org/vks/v1/by-fingerprint/33727F5377D296C320AF704AB3BD6196E1CFBFB4) to encrypt if your report contains sensitive technical details.
|
||||
|
||||
### Information to Include
|
||||
1. **Description** of the vulnerability.
|
||||
2. **Steps to reproduce** the issue.
|
||||
3. **Impact assessment** (who is affected, what's at risk).
|
||||
4. **Suggested fix** (if you have one).
|
||||
5. **Your contact information** for follow-up.
|
||||
|
||||
1. **Description** of the vulnerability
|
||||
2. **Steps to reproduce** the issue
|
||||
3. **Impact assessment** (who is affected, what's at risk)
|
||||
4. **Suggested fix** (if you have one)
|
||||
5. **Your contact information** for follow-up
|
||||
|
||||
### What to Expect
|
||||
- **Acknowledgment:** within 48 hours
|
||||
- **Initial assessment:** within 1 week
|
||||
- **Status updates:** every 2 weeks until resolved
|
||||
|
||||
**Resolution timelines**
|
||||
- **Acknowledgment:** within 48 hours
|
||||
- **Initial assessment:** within 1 week
|
||||
- **Status updates:** every 2 weeks until resolved
|
||||
|
||||
**Resolution timelines:**
|
||||
|
||||
| Severity | Response Time |
|
||||
|-----------|----------------|
|
||||
| Critical | 1 to 7 days |
|
||||
| High | 1 to 4 weeks |
|
||||
| Medium | 1 to 3 months |
|
||||
| Critical | 1-7 days |
|
||||
| High | 1-4 weeks |
|
||||
| Medium | 1-3 months |
|
||||
| Low | Next release cycle |
|
||||
|
||||
### Coordinated Disclosure
|
||||
|
||||
We follow responsible disclosure practices:
|
||||
1. **Report received** → We acknowledge and investigate.
|
||||
2. **Fix developed** → We create and test a patch.
|
||||
3. **Coordinated release** → We agree on disclosure timing.
|
||||
4. **Public disclosure** → We release the fix and advisory.
|
||||
5. **Credit given** → We acknowledge the reporter (unless anonymity is requested).
|
||||
1. **Report received** → We acknowledge and investigate
|
||||
2. **Fix developed** → We create and test a patch
|
||||
3. **Coordinated release** → We agree on disclosure timing
|
||||
4. **Public disclosure** → We release the fix and advisory
|
||||
5. **Credit given** → We acknowledge the reporter (unless anonymity is requested)
|
||||
|
||||
---
|
||||
|
||||
@@ -84,7 +310,31 @@ We follow responsible disclosure practices:
|
||||
|
||||
### For Relay Operators
|
||||
|
||||
#### Port Security Configuration
|
||||
|
||||
```bash
|
||||
# Verify only required ports are exposed
|
||||
docker ps --format "table {{.Names}}\t{{.Ports}}"
|
||||
|
||||
# Expected output should show ONLY:
|
||||
# 0.0.0.0:9001->9001/tcp
|
||||
# 0.0.0.0:9030->9030/tcp
|
||||
|
||||
# Check internal services bind to localhost
|
||||
docker exec guard-relay netstat -tlnp | grep -E "9035|9036|9037"
|
||||
# Should show 127.0.0.1:PORT only
|
||||
|
||||
# Test external accessibility (should fail for metrics)
|
||||
curl -m 5 http://YOUR_PUBLIC_IP:9035/metrics
|
||||
# Should timeout or be refused
|
||||
|
||||
# Test internal accessibility (should work from container)
|
||||
docker exec guard-relay curl -s http://127.0.0.1:9035/metrics
|
||||
# Should return Prometheus metrics
|
||||
```
|
||||
|
||||
#### Configuration Security
|
||||
|
||||
```bash
|
||||
# Secure your relay.conf file
|
||||
chmod 600 /path/to/relay.conf
|
||||
@@ -92,7 +342,7 @@ chown root:root /path/to/relay.conf
|
||||
|
||||
# Use read-only mounts
|
||||
-v /path/to/relay.conf:/etc/tor/torrc:ro
|
||||
````
|
||||
```
|
||||
|
||||
#### Contact Information
|
||||
|
||||
@@ -107,16 +357,13 @@ ContactInfo your-email proof:uri-rsa abuse:abuse@example.com
|
||||
#### Network Security
|
||||
|
||||
```bash
|
||||
# Firewall rules (UFW example)
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow 9001/tcp # ORPort
|
||||
sudo ufw allow 22/tcp # SSH (be careful!)
|
||||
sudo ufw enable
|
||||
|
||||
# Regular security updates
|
||||
apt update && apt upgrade -y # Ubuntu/Debian
|
||||
yum update -y # RHEL/CentOS
|
||||
|
||||
# Verify firewall rules
|
||||
sudo ufw status numbered
|
||||
sudo iptables -L -n -v
|
||||
```
|
||||
|
||||
#### Monitoring
|
||||
@@ -126,10 +373,13 @@ yum update -y # RHEL/CentOS
|
||||
docker logs guard-relay 2>&1 | grep -iE "(error|warn|critical)"
|
||||
|
||||
# Scheduled health checks
|
||||
0 */6 * * * docker exec guard-relay relay-status >> /var/log/relay-check.log
|
||||
0 */6 * * * docker exec guard-relay status >> /var/log/relay-check.log
|
||||
|
||||
# Resource monitoring
|
||||
docker stats guard-relay --no-stream
|
||||
|
||||
# Port accessibility audit
|
||||
docker exec guard-relay net-check
|
||||
```
|
||||
|
||||
---
|
||||
@@ -138,17 +388,17 @@ docker stats guard-relay --no-stream
|
||||
|
||||
#### Code Security
|
||||
|
||||
* Never commit secrets or API keys.
|
||||
* Use `.gitignore` for sensitive files.
|
||||
* Review dependencies for vulnerabilities.
|
||||
* Follow the principle of least privilege.
|
||||
* Validate all user inputs.
|
||||
* Never commit secrets or API keys
|
||||
* Use `.gitignore` for sensitive files
|
||||
* Review dependencies for vulnerabilities
|
||||
* Follow the principle of least privilege
|
||||
* Validate all user inputs
|
||||
|
||||
#### Docker Security
|
||||
|
||||
```dockerfile
|
||||
# Always specify explicit base version
|
||||
FROM alpine:edge # Pinned dynamically in CI for latest security patches
|
||||
FROM alpine:3.22.2 # Pinned version for reproducibility
|
||||
|
||||
# Run as non-root user
|
||||
USER tor
|
||||
@@ -176,27 +426,26 @@ echo "relay.conf" >> .gitignore
|
||||
|
||||
### Host Network Mode
|
||||
|
||||
**What:** Container uses `--network host`
|
||||
**What:** Container uses `--network host`
|
||||
**Why:** Enables Tor dual-stack (IPv4 + IPv6) support
|
||||
|
||||
**Security Impact:**
|
||||
|
||||
* ✅ Container cannot access other containers
|
||||
* ✅ Runs as non-root user (`tor`)
|
||||
* ⚠️ Shares host network namespace
|
||||
* ⚠️ Can bind to any host port
|
||||
* ⚠️ Can bind to any host port (mitigated by localhost binding)
|
||||
|
||||
**Mitigations:**
|
||||
|
||||
* Drops unnecessary capabilities
|
||||
* Uses `no-new-privileges:true`
|
||||
* Only grants required capabilities
|
||||
* Internal services bind to 127.0.0.1 only
|
||||
* Relies on proper firewall configuration
|
||||
|
||||
### Volume Permissions
|
||||
|
||||
**What:** Persistent volumes store keys and state.
|
||||
**Security Impact:** Keys live in `/var/lib/tor`; protect them from unauthorized access.
|
||||
**What:** Persistent volumes store keys and state
|
||||
**Security Impact:** Keys live in `/var/lib/tor`; protect from unauthorized access
|
||||
|
||||
**Mitigation:**
|
||||
|
||||
@@ -211,11 +460,10 @@ chown tor:tor /var/lib/tor
|
||||
|
||||
### Configuration Exposure
|
||||
|
||||
**What:** Configuration is mounted from the host.
|
||||
**What:** Configuration is mounted from the host
|
||||
**Impact:** May reveal bandwidth limits, ContactInfo, etc.
|
||||
|
||||
**Mitigation:**
|
||||
|
||||
* Use read-only mount (`:ro`)
|
||||
* Set restrictive file permissions (600)
|
||||
* Never commit configs
|
||||
@@ -233,15 +481,31 @@ chown tor:tor /var/lib/tor
|
||||
* ✅ Read-only configuration mount
|
||||
* ✅ Automatic permission healing
|
||||
* ✅ Configuration validation on startup
|
||||
* ✅ Localhost-only binding for internal services
|
||||
|
||||
### Port Binding Security
|
||||
|
||||
**External (public) services:**
|
||||
```
|
||||
ORPort 9001 # Binds to 0.0.0.0:9001
|
||||
ORPort [::]:9001 # Binds to [::]:9001 (IPv6)
|
||||
DirPort 9030 # Binds to 0.0.0.0:9030
|
||||
```
|
||||
|
||||
**Internal (localhost-only) services:**
|
||||
```
|
||||
metrics-http binds to 127.0.0.1:9035
|
||||
health API binds to 127.0.0.1:9036
|
||||
dashboard binds to 127.0.0.1:9037
|
||||
```
|
||||
|
||||
### Weekly Security Updates
|
||||
|
||||
To ensure ongoing hardening, CI automatically:
|
||||
|
||||
1. Pulls latest Alpine base.
|
||||
2. Installs updated Tor.
|
||||
3. Applies available patches.
|
||||
4. Rebuilds and republishes to GHCR.
|
||||
1. Pulls latest Alpine base
|
||||
2. Installs updated Tor
|
||||
3. Applies available patches
|
||||
4. Rebuilds and republishes to GHCR
|
||||
|
||||
Enable automatic updates in Cosmos:
|
||||
|
||||
@@ -257,28 +521,26 @@ Enable automatic updates in Cosmos:
|
||||
### Tor Network Participation
|
||||
|
||||
Running a Tor relay is legal in most countries, but:
|
||||
|
||||
* ⚠️ Check local laws and ISP terms of service.
|
||||
* ⚠️ Understand exit vs. guard relay differences.
|
||||
* ⚠️ Keep contact information accurate.
|
||||
* ⚠️ Check local laws and ISP terms of service
|
||||
* ⚠️ Understand exit vs. guard relay differences
|
||||
* ⚠️ Keep contact information accurate
|
||||
|
||||
**This project runs GUARD relays (not exit relays) by default.**
|
||||
|
||||
### Data Handling
|
||||
|
||||
* Tor relays do **not** log traffic content.
|
||||
* Relay fingerprints are public.
|
||||
* Contact information and bandwidth statistics are public.
|
||||
* Tor relays do **not** log traffic content
|
||||
* Relay fingerprints are public
|
||||
* Contact information and bandwidth statistics are public
|
||||
|
||||
### Abuse Handling
|
||||
|
||||
If you receive abuse complaints:
|
||||
|
||||
1. Verify it’s actually your relay.
|
||||
2. Review Tor Project [abuse response templates](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq/).
|
||||
3. Respond professionally.
|
||||
4. Consider your legal position.
|
||||
5. Join [tor-relays mailing list](https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-relays) for help.
|
||||
1. Verify it's actually your relay
|
||||
2. Review Tor Project [abuse response templates](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq/)
|
||||
3. Respond professionally
|
||||
4. Consider your legal position
|
||||
5. Join [tor-relays mailing list](https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-relays) for help
|
||||
|
||||
---
|
||||
|
||||
@@ -303,9 +565,65 @@ If you receive abuse complaints:
|
||||
|
||||
---
|
||||
|
||||
## Network Security Audit
|
||||
|
||||
### Quick Security Checklist
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# security-audit.sh - Quick security audit for Tor Guard Relay
|
||||
|
||||
echo "🔒 Tor Guard Relay Security Audit"
|
||||
echo "=================================="
|
||||
|
||||
# Check exposed ports
|
||||
echo ""
|
||||
echo "1. Checking exposed ports..."
|
||||
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep guard-relay
|
||||
|
||||
# Check internal service bindings
|
||||
echo ""
|
||||
echo "2. Checking internal service bindings..."
|
||||
docker exec guard-relay netstat -tlnp 2>/dev/null | grep -E "9035|9036|9037" || echo "No internal services detected"
|
||||
|
||||
# Test external accessibility
|
||||
echo ""
|
||||
echo "3. Testing external port accessibility..."
|
||||
PUBLIC_IP=$(curl -s https://icanhazip.com)
|
||||
timeout 5 nc -zv $PUBLIC_IP 9001 && echo "✅ ORPort 9001 accessible" || echo "❌ ORPort 9001 not accessible"
|
||||
timeout 5 nc -zv $PUBLIC_IP 9030 && echo "✅ DirPort 9030 accessible" || echo "⚠️ DirPort 9030 not accessible"
|
||||
|
||||
# Test metrics should NOT be externally accessible
|
||||
echo ""
|
||||
echo "4. Testing metrics port (should NOT be accessible externally)..."
|
||||
timeout 5 curl -s http://$PUBLIC_IP:9035/metrics && echo "❌ SECURITY ISSUE: Metrics exposed!" || echo "✅ Metrics properly secured"
|
||||
|
||||
# Check file permissions
|
||||
echo ""
|
||||
echo "5. Checking critical file permissions..."
|
||||
docker exec guard-relay ls -la /var/lib/tor | grep -E "keys|fingerprint"
|
||||
|
||||
# Check user
|
||||
echo ""
|
||||
echo "6. Checking process user..."
|
||||
docker exec guard-relay ps aux | grep tor | head -1
|
||||
|
||||
# Check capabilities
|
||||
echo ""
|
||||
echo "7. Checking container capabilities..."
|
||||
docker inspect guard-relay --format='{{.HostConfig.CapDrop}}' | grep -q "ALL" && echo "✅ All capabilities dropped" || echo "⚠️ Capabilities not fully restricted"
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "Audit complete!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hall of Fame 🏆
|
||||
|
||||
Security researchers who responsibly disclose vulnerabilities will be listed here:
|
||||
|
||||
*No vulnerabilities reported yet.*
|
||||
|
||||
---
|
||||
@@ -320,4 +638,6 @@ Security researchers who responsibly disclose vulnerabilities will be listed her
|
||||
|
||||
**Thank you for helping keep this project secure!** 🔒🧅
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
*Last Updated: 2025-11-05 | Version: 1.0.2*
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
# docker-entrypoint.sh - Tor Guard Relay initialization and process management
|
||||
# Handles startup sequence: preflight checks → configuration validation → health monitoring →
|
||||
# metrics exposure → main Tor process, with proper signal handling and background process management
|
||||
|
||||
set -euo pipefail
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
readonly TOR_CONFIG="${TOR_CONFIG:-/etc/tor/torrc}"
|
||||
@@ -15,17 +15,44 @@ readonly ENABLE_METRICS="${ENABLE_METRICS:-false}"
|
||||
readonly ENABLE_HEALTH_CHECK="${ENABLE_HEALTH_CHECK:-true}"
|
||||
readonly ENABLE_NET_CHECK="${ENABLE_NET_CHECK:-false}"
|
||||
|
||||
# Signal handlers for clean shutdown
|
||||
trap 'on_sigterm' SIGTERM SIGINT
|
||||
# 🔒 NEW: Global PID tracking for cleanup
|
||||
TOR_PID=""
|
||||
METRICS_PID=""
|
||||
HEALTH_PID=""
|
||||
|
||||
on_sigterm() {
|
||||
# 🔒 NEW: Improved signal handler with comprehensive cleanup
|
||||
trap 'cleanup_and_exit' SIGTERM SIGINT
|
||||
|
||||
cleanup_and_exit() {
|
||||
echo ""
|
||||
echo "🛑 Shutdown signal received. Gracefully stopping Tor..."
|
||||
if [ -n "${TOR_PID:-}" ]; then
|
||||
echo "🛑 Shutdown signal received. Stopping all services..."
|
||||
|
||||
# Kill background services first (reverse order of startup)
|
||||
if [ -n "$HEALTH_PID" ] && kill -0 "$HEALTH_PID" 2>/dev/null; then
|
||||
echo " Stopping health monitor (PID: $HEALTH_PID)..."
|
||||
kill -TERM "$HEALTH_PID" 2>/dev/null || true
|
||||
# Give it a moment to exit gracefully
|
||||
sleep 1
|
||||
# Force kill if still running
|
||||
kill -9 "$HEALTH_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -n "$METRICS_PID" ] && kill -0 "$METRICS_PID" 2>/dev/null; then
|
||||
echo " Stopping metrics service (PID: $METRICS_PID)..."
|
||||
kill -TERM "$METRICS_PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -9 "$METRICS_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Finally, stop Tor relay
|
||||
if [ -n "$TOR_PID" ] && kill -0 "$TOR_PID" 2>/dev/null; then
|
||||
echo " Stopping Tor relay (PID: $TOR_PID)..."
|
||||
kill -TERM "$TOR_PID" 2>/dev/null || true
|
||||
# Wait for Tor to shut down gracefully (up to 30 seconds)
|
||||
wait "$TOR_PID" 2>/dev/null || true
|
||||
fi
|
||||
echo "✅ Tor relay shut down cleanly."
|
||||
|
||||
echo "✅ All services stopped cleanly."
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -109,13 +136,13 @@ validation_phase() {
|
||||
|
||||
echo "🔍 Phase 5: Preflight Diagnostics"
|
||||
echo " 🌐 Checking basic network connectivity..."
|
||||
if ping -c1 -W2 1.1.1.1 >/dev/null 2>&1; then
|
||||
if ping -c1 -W2 ipv4.icanhazip.com >/dev/null 2>&1; then
|
||||
echo " ✓ IPv4 connectivity OK"
|
||||
else
|
||||
echo " ⚠️ IPv4 connectivity unavailable"
|
||||
fi
|
||||
|
||||
if ping6 -c1 -W2 ipv6.google.com >/dev/null 2>&1; then
|
||||
if ping6 -c1 -W2 ipv6.icanhazip.com >/dev/null 2>&1; then
|
||||
echo " ✓ IPv6 connectivity OK"
|
||||
else
|
||||
echo " ⚠️ IPv6 connectivity unavailable"
|
||||
@@ -134,9 +161,6 @@ validation_phase() {
|
||||
echo " ⏭️ Skipping extended network diagnostics (ENABLE_NET_CHECK=false)"
|
||||
fi
|
||||
|
||||
# NOTE: Health check skipped here intentionally.
|
||||
# Tor is not yet started at this stage.
|
||||
# Health monitoring begins later in start_health_service().
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -162,7 +186,7 @@ buildinfo_phase() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Metrics service (background)
|
||||
# 🔒 IMPROVED: Metrics service with PID tracking
|
||||
start_metrics_service() {
|
||||
if [ "$ENABLE_METRICS" != "true" ]; then
|
||||
echo "📊 Phase 7: Metrics Service disabled (ENABLE_METRICS=false)"
|
||||
@@ -177,13 +201,22 @@ start_metrics_service() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Start metrics service and capture PID
|
||||
metrics-http "$METRICS_PORT" &
|
||||
METRICS_PID=$!
|
||||
echo " ✓ Metrics service active on port $METRICS_PORT (PID: $METRICS_PID)"
|
||||
|
||||
# Verify process started successfully
|
||||
sleep 1
|
||||
if kill -0 "$METRICS_PID" 2>/dev/null; then
|
||||
echo " ✓ Metrics service active on port $METRICS_PORT (PID: $METRICS_PID)"
|
||||
else
|
||||
echo " ⚠️ Metrics service failed to start"
|
||||
METRICS_PID=""
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Health check service (background)
|
||||
# 🔒 IMPROVED: Health check service with PID tracking
|
||||
start_health_service() {
|
||||
if [ "$ENABLE_HEALTH_CHECK" != "true" ]; then
|
||||
echo "💚 Phase 8: Health Check Service disabled (ENABLE_HEALTH_CHECK=false)"
|
||||
@@ -192,6 +225,8 @@ start_health_service() {
|
||||
fi
|
||||
|
||||
echo "💚 Phase 8: Starting Health Check Service"
|
||||
|
||||
# Start health monitor in background and capture PID
|
||||
(
|
||||
while true; do
|
||||
sleep 30
|
||||
@@ -207,7 +242,15 @@ start_health_service() {
|
||||
done
|
||||
) &
|
||||
HEALTH_PID=$!
|
||||
echo " ✓ Health monitor active (PID: $HEALTH_PID)"
|
||||
|
||||
# Verify process started successfully
|
||||
sleep 1
|
||||
if kill -0 "$HEALTH_PID" 2>/dev/null; then
|
||||
echo " ✓ Health monitor active (PID: $HEALTH_PID)"
|
||||
else
|
||||
echo " ⚠️ Health monitor failed to start"
|
||||
HEALTH_PID=""
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -230,9 +273,27 @@ startup_message() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main Tor process
|
||||
# 🔒 IMPROVED: Tor process launcher with PID tracking
|
||||
run_tor() {
|
||||
exec "$@"
|
||||
# Start Tor in background so we can track its PID
|
||||
"$@" &
|
||||
TOR_PID=$!
|
||||
|
||||
echo " ✓ Tor relay started (PID: $TOR_PID)"
|
||||
echo ""
|
||||
|
||||
# Wait for Tor process to complete
|
||||
# This blocks until Tor exits or signal is received
|
||||
wait "$TOR_PID"
|
||||
|
||||
# If we reach here, Tor exited on its own (not from signal)
|
||||
TOR_EXIT_CODE=$?
|
||||
|
||||
echo ""
|
||||
echo "🛑 Tor process exited with code: $TOR_EXIT_CODE"
|
||||
|
||||
# Cleanup background services
|
||||
cleanup_and_exit
|
||||
}
|
||||
|
||||
# Main execution flow
|
||||
@@ -246,4 +307,4 @@ main() {
|
||||
run_tor "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# integration-check.sh - Master integration test runner for Tor Guard Relay
|
||||
# Validates all tools are present, executable, and produce correct output formats
|
||||
# integration-check.sh - Master integration test runner for Tor Guard Relay v1.0.2
|
||||
# Validates all tools, port security, and configuration compliance
|
||||
# Returns: 0 on success, 1 on failure; outputs emoji-based summary
|
||||
|
||||
set -euo pipefail
|
||||
@@ -10,12 +10,14 @@ readonly CONTAINER="${CONTAINER:-guard-relay}"
|
||||
readonly TOOLS_DIR="/usr/local/bin"
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
readonly VERSION="1.0.2"
|
||||
|
||||
# State tracking
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
WARN_COUNT=0
|
||||
declare -a RESULTS=()
|
||||
declare -a SECURITY_ISSUES=()
|
||||
|
||||
# Colors for terminal output (safe in Alpine)
|
||||
readonly RED='\033[0;31m'
|
||||
@@ -23,6 +25,7 @@ readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly BLUE='\033[0;34m'
|
||||
readonly CYAN='\033[0;36m'
|
||||
readonly MAGENTA='\033[0;35m'
|
||||
readonly NC='\033[0m'
|
||||
|
||||
# Output functions
|
||||
@@ -58,6 +61,11 @@ log_info() {
|
||||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
log_security() {
|
||||
echo -e "${MAGENTA}🔒${NC} $1"
|
||||
SECURITY_ISSUES+=("$1")
|
||||
}
|
||||
|
||||
# Test functions
|
||||
test_file_exists() {
|
||||
local path=$1
|
||||
@@ -130,42 +138,6 @@ test_text_contains() {
|
||||
fi
|
||||
}
|
||||
|
||||
test_tool_output_format() {
|
||||
local tool_path=$1
|
||||
local tool_name=$2
|
||||
local expected_format=$3
|
||||
|
||||
if [ ! -f "$tool_path" ]; then
|
||||
log_fail "$tool_name not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local output
|
||||
output=$("$tool_path" 2>&1 || true)
|
||||
|
||||
case "$expected_format" in
|
||||
json)
|
||||
test_json_valid "$output" "$tool_name JSON"
|
||||
;;
|
||||
text)
|
||||
if [ -n "$output" ]; then
|
||||
log_pass "$tool_name produces text output"
|
||||
else
|
||||
log_fail "$tool_name produces empty output"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
html)
|
||||
if echo "$output" | grep -q "<!DOCTYPE\|<html"; then
|
||||
log_pass "$tool_name produces HTML output"
|
||||
else
|
||||
log_fail "$tool_name does not produce HTML"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Phase 1: File Existence & Permissions
|
||||
phase_1_files() {
|
||||
log_section "Phase 1: File Existence & Permissions"
|
||||
@@ -178,6 +150,8 @@ phase_1_files() {
|
||||
test_file_exists "$TOOLS_DIR/metrics" "metrics"
|
||||
test_file_exists "$TOOLS_DIR/metrics-http" "metrics-http"
|
||||
test_file_exists "$TOOLS_DIR/dashboard" "dashboard"
|
||||
test_file_exists "$TOOLS_DIR/setup" "setup"
|
||||
test_file_exists "$TOOLS_DIR/net-check" "net-check"
|
||||
|
||||
# Check docker-entrypoint.sh
|
||||
test_file_exists "$TOOLS_DIR/docker-entrypoint.sh" "docker-entrypoint.sh"
|
||||
@@ -199,6 +173,8 @@ phase_2_permissions() {
|
||||
test_file_executable "$TOOLS_DIR/metrics" "metrics"
|
||||
test_file_executable "$TOOLS_DIR/metrics-http" "metrics-http"
|
||||
test_file_executable "$TOOLS_DIR/dashboard" "dashboard"
|
||||
test_file_executable "$TOOLS_DIR/setup" "setup"
|
||||
test_file_executable "$TOOLS_DIR/net-check" "net-check"
|
||||
test_file_executable "$TOOLS_DIR/docker-entrypoint.sh" "docker-entrypoint.sh"
|
||||
}
|
||||
|
||||
@@ -206,7 +182,7 @@ phase_2_permissions() {
|
||||
phase_3_syntax() {
|
||||
log_section "Phase 3: Shell Syntax Validation"
|
||||
|
||||
for tool in status fingerprint view-logs health metrics metrics-http dashboard docker-entrypoint.sh; do
|
||||
for tool in status fingerprint view-logs health metrics metrics-http dashboard setup net-check docker-entrypoint.sh; do
|
||||
if [ -f "$TOOLS_DIR/$tool" ]; then
|
||||
test_shell_syntax "$TOOLS_DIR/$tool" "$tool"
|
||||
fi
|
||||
@@ -215,6 +191,10 @@ phase_3_syntax() {
|
||||
if [ -f "$SCRIPT_DIR/integration-check.sh" ]; then
|
||||
test_shell_syntax "$SCRIPT_DIR/integration-check.sh" "integration-check.sh"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/relay-status.sh" ]; then
|
||||
test_shell_syntax "$SCRIPT_DIR/relay-status.sh" "relay-status.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Phase 4: Directory Structure
|
||||
@@ -230,6 +210,14 @@ phase_4_directories() {
|
||||
|
||||
if [ -d "/var/lib/tor" ]; then
|
||||
log_pass "Tor data directory exists: /var/lib/tor"
|
||||
|
||||
# Check permissions
|
||||
local perms=$(stat -c "%a" /var/lib/tor 2>/dev/null || echo "unknown")
|
||||
if [ "$perms" = "700" ] || [ "$perms" = "750" ]; then
|
||||
log_pass "Tor data directory has secure permissions: $perms"
|
||||
else
|
||||
log_warn "Tor data directory permissions: $perms (should be 700 or 750)"
|
||||
fi
|
||||
else
|
||||
log_fail "Tor data directory missing: /var/lib/tor"
|
||||
fi
|
||||
@@ -247,9 +235,82 @@ phase_4_directories() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Phase 5: Output Format Validation (requires execution)
|
||||
phase_5_output_formats() {
|
||||
log_section "Phase 5: Output Format Validation"
|
||||
# Phase 5: Port Security Validation (CRITICAL for v1.0.2)
|
||||
phase_5_port_security() {
|
||||
log_section "Phase 5: Port Security Validation"
|
||||
|
||||
log_info "Validating port exposure policy (9001/9030 only)..."
|
||||
|
||||
# Check if metrics-http binds to localhost only
|
||||
if [ -f "$TOOLS_DIR/metrics-http" ]; then
|
||||
local bind_check=$(grep -E "127\.0\.0\.1|localhost" "$TOOLS_DIR/metrics-http" 2>/dev/null || echo "")
|
||||
if [ -n "$bind_check" ]; then
|
||||
log_pass "metrics-http configured for localhost binding"
|
||||
else
|
||||
log_security "SECURITY: metrics-http may not be localhost-only"
|
||||
log_fail "metrics-http localhost binding not confirmed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if dashboard binds to localhost only
|
||||
if [ -f "$TOOLS_DIR/dashboard" ]; then
|
||||
local bind_check=$(grep -E "127\.0\.0\.1|localhost" "$TOOLS_DIR/dashboard" 2>/dev/null || echo "")
|
||||
if [ -n "$bind_check" ]; then
|
||||
log_pass "dashboard configured for localhost binding"
|
||||
else
|
||||
log_warn "dashboard localhost binding not confirmed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check torrc for proper port configuration (if exists)
|
||||
if [ -f "/etc/tor/torrc" ]; then
|
||||
log_info "Checking torrc port configuration..."
|
||||
|
||||
# Check for ORPort 9001
|
||||
if grep -q "^ORPort 9001" /etc/tor/torrc 2>/dev/null; then
|
||||
log_pass "ORPort 9001 configured correctly"
|
||||
else
|
||||
log_warn "ORPort 9001 not found in torrc"
|
||||
fi
|
||||
|
||||
# Check for DirPort 9030
|
||||
if grep -q "^DirPort 9030" /etc/tor/torrc 2>/dev/null; then
|
||||
log_pass "DirPort 9030 configured correctly"
|
||||
else
|
||||
log_info "DirPort 9030 not configured (optional)"
|
||||
fi
|
||||
|
||||
# Check that SocksPort is disabled
|
||||
if grep -q "^SocksPort 0" /etc/tor/torrc 2>/dev/null; then
|
||||
log_pass "SocksPort properly disabled"
|
||||
else
|
||||
log_warn "SocksPort setting not confirmed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for any references to dangerous port exposure
|
||||
log_info "Scanning for improper port exposure patterns..."
|
||||
|
||||
local dangerous_patterns=0
|
||||
for tool in "$TOOLS_DIR"/*; do
|
||||
if [ -f "$tool" ] && [ -x "$tool" ]; then
|
||||
# Look for 0.0.0.0 bindings on non-Tor ports
|
||||
if grep -qE "0\.0\.0\.0:903[5-9]|0\.0\.0\.0:904[0-9]" "$tool" 2>/dev/null; then
|
||||
log_security "SECURITY: Found potential 0.0.0.0 binding in $(basename $tool)"
|
||||
log_fail "$(basename $tool) may expose internal ports"
|
||||
((dangerous_patterns++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $dangerous_patterns -eq 0 ]; then
|
||||
log_pass "No dangerous port exposure patterns detected"
|
||||
fi
|
||||
}
|
||||
|
||||
# Phase 6: Output Format Validation
|
||||
phase_6_output_formats() {
|
||||
log_section "Phase 6: Output Format Validation"
|
||||
|
||||
# health (JSON)
|
||||
if [ -f "$TOOLS_DIR/health" ]; then
|
||||
@@ -302,11 +363,22 @@ phase_5_output_formats() {
|
||||
log_warn "dashboard HTML validation not fully checked"
|
||||
fi
|
||||
fi
|
||||
|
||||
# net-check (text with emoji)
|
||||
if [ -f "$TOOLS_DIR/net-check" ]; then
|
||||
log_info "Testing net-check output format..."
|
||||
NETCHECK_OUT=$("$TOOLS_DIR/net-check" 2>&1 || echo "Network check")
|
||||
if echo "$NETCHECK_OUT" | grep -qE "🌐|Network|Diagnostics"; then
|
||||
log_pass "net-check produces expected output"
|
||||
else
|
||||
log_warn "net-check output not fully validated"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Phase 6: Environment Variables
|
||||
phase_6_environment() {
|
||||
log_section "Phase 6: Environment Variables"
|
||||
# Phase 7: Environment Variables
|
||||
phase_7_environment() {
|
||||
log_section "Phase 7: Environment Variables"
|
||||
|
||||
if [ -n "${TOR_DATA_DIR:-}" ]; then
|
||||
log_pass "TOR_DATA_DIR is set: $TOR_DATA_DIR"
|
||||
@@ -327,6 +399,52 @@ phase_6_environment() {
|
||||
log_fail "PATH missing /usr/local/bin: $PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for metrics-related env vars
|
||||
if [ -n "${ENABLE_METRICS:-}" ]; then
|
||||
log_info "ENABLE_METRICS is set: $ENABLE_METRICS"
|
||||
fi
|
||||
|
||||
if [ -n "${METRICS_PORT:-}" ]; then
|
||||
log_info "METRICS_PORT is set: $METRICS_PORT"
|
||||
# Validate it's in the proper range
|
||||
if [ "$METRICS_PORT" -ge 9035 ] && [ "$METRICS_PORT" -le 9099 ]; then
|
||||
log_pass "METRICS_PORT in valid range: $METRICS_PORT"
|
||||
else
|
||||
log_warn "METRICS_PORT outside recommended range: $METRICS_PORT"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Phase 8: Version Validation
|
||||
phase_8_version() {
|
||||
log_section "Phase 8: Version Validation"
|
||||
|
||||
log_info "Integration check version: $VERSION"
|
||||
|
||||
# Check if build-info.txt exists and contains version
|
||||
if [ -f "/build-info.txt" ]; then
|
||||
local build_version=$(grep "Version:" /build-info.txt 2>/dev/null | cut -d: -f2 | tr -d ' ' || echo "unknown")
|
||||
log_info "Build version: $build_version"
|
||||
|
||||
if [ "$build_version" = "$VERSION" ] || [ "$build_version" = "v$VERSION" ]; then
|
||||
log_pass "Build version matches integration check version"
|
||||
else
|
||||
log_warn "Build version mismatch (expected: $VERSION, found: $build_version)"
|
||||
fi
|
||||
else
|
||||
log_info "No build-info.txt found (may be normal in development)"
|
||||
fi
|
||||
|
||||
# Check relay-status.sh version if available
|
||||
if [ -f "$SCRIPT_DIR/relay-status.sh" ]; then
|
||||
local script_version=$(grep "^readonly VERSION=" "$SCRIPT_DIR/relay-status.sh" 2>/dev/null | cut -d'"' -f2 || echo "unknown")
|
||||
if [ "$script_version" = "$VERSION" ]; then
|
||||
log_pass "relay-status.sh version matches: $VERSION"
|
||||
else
|
||||
log_warn "relay-status.sh version mismatch (expected: $VERSION, found: $script_version)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate Summary Report
|
||||
@@ -334,30 +452,44 @@ generate_summary() {
|
||||
local total=$((PASS_COUNT + FAIL_COUNT + WARN_COUNT))
|
||||
|
||||
echo ""
|
||||
log_header "🧅 Integration Test Summary"
|
||||
log_header "🧅 Integration Test Summary (v$VERSION)"
|
||||
|
||||
echo ""
|
||||
echo "Total Tests: $total"
|
||||
echo -e "${GREEN}Passed: ${PASS_COUNT}${NC}"
|
||||
echo -e "${GREEN}✓ Passed: ${PASS_COUNT}${NC}"
|
||||
|
||||
if [ $WARN_COUNT -gt 0 ]; then
|
||||
echo -e "${YELLOW}Warnings: ${WARN_COUNT}${NC}"
|
||||
echo -e "${YELLOW}⚠ Warnings: ${WARN_COUNT}${NC}"
|
||||
fi
|
||||
|
||||
if [ $FAIL_COUNT -gt 0 ]; then
|
||||
echo -e "${RED}Failed: ${FAIL_COUNT}${NC}"
|
||||
echo -e "${RED}✗ Failed: ${FAIL_COUNT}${NC}"
|
||||
fi
|
||||
|
||||
# Security issues summary
|
||||
if [ ${#SECURITY_ISSUES[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${MAGENTA}🔒 Security Issues Detected: ${#SECURITY_ISSUES[@]}${NC}"
|
||||
for issue in "${SECURITY_ISSUES[@]}"; do
|
||||
echo -e " ${MAGENTA}•${NC} $issue"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Results:"
|
||||
echo "Detailed Results:"
|
||||
for result in "${RESULTS[@]}"; do
|
||||
echo " $result"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $FAIL_COUNT -eq 0 ]; then
|
||||
if [ $FAIL_COUNT -eq 0 ] && [ ${#SECURITY_ISSUES[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ All integration checks passed!${NC}"
|
||||
echo -e "${GREEN}✅ No security issues detected!${NC}"
|
||||
return 0
|
||||
elif [ $FAIL_COUNT -eq 0 ] && [ ${#SECURITY_ISSUES[@]} -gt 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ Integration checks passed with security warnings.${NC}"
|
||||
echo -e "${YELLOW}⚠️ Please review security issues above.${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ Some integration checks failed.${NC}"
|
||||
@@ -367,9 +499,10 @@ generate_summary() {
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log_header "🧅 Tor Guard Relay Integration Check v1.0"
|
||||
log_header "🧅 Tor Guard Relay Integration Check v$VERSION"
|
||||
log_info "Timestamp: $TIMESTAMP"
|
||||
log_info "Container: $CONTAINER"
|
||||
log_info "Target Release: v1.0.2"
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -377,8 +510,10 @@ main() {
|
||||
phase_2_permissions
|
||||
phase_3_syntax
|
||||
phase_4_directories
|
||||
phase_5_output_formats
|
||||
phase_6_environment
|
||||
phase_5_port_security
|
||||
phase_6_output_formats
|
||||
phase_7_environment
|
||||
phase_8_version
|
||||
|
||||
echo ""
|
||||
generate_summary
|
||||
|
||||
137
relay-status.sh
137
relay-status.sh
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# relay-status.sh — Tor relay/bridge status checker
|
||||
# relay-status.sh — Tor relay/bridge status checker with security validation
|
||||
# Version: 1.0.2
|
||||
# Automatically detects Tor containers or uses specified container name
|
||||
#
|
||||
|
||||
@@ -10,6 +11,7 @@ set -euo pipefail
|
||||
CONTAINER="${1:-}" # Accept container name as first argument
|
||||
readonly FINGERPRINT_PATH="/var/lib/tor/fingerprint"
|
||||
readonly TORRC_PATH="/etc/tor/torrc"
|
||||
readonly VERSION="1.0.2"
|
||||
|
||||
# Colors for output
|
||||
readonly RED='\033[0;31m'
|
||||
@@ -103,6 +105,70 @@ check_container() {
|
||||
if [[ -n "${image}" ]]; then
|
||||
echo -e " ${BLUE}Image:${NC} ${image}"
|
||||
fi
|
||||
|
||||
# Check build info if available
|
||||
local build_info
|
||||
build_info=$(sudo docker exec "${CONTAINER}" cat /build-info.txt 2>/dev/null || echo "")
|
||||
if [[ -n "${build_info}" ]]; then
|
||||
echo -e " ${BLUE}Build:${NC}"
|
||||
echo "${build_info}" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate port security configuration
|
||||
check_port_security() {
|
||||
print_section "Port Security Validation"
|
||||
|
||||
# Check exposed ports
|
||||
local exposed_ports
|
||||
exposed_ports=$(sudo docker port "${CONTAINER}" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "${exposed_ports}" ]]; then
|
||||
echo -e " ${BLUE}Exposed ports:${NC}"
|
||||
echo "${exposed_ports}" | sed 's/^/ /'
|
||||
|
||||
# Validate only 9001 and 9030 are exposed
|
||||
if echo "${exposed_ports}" | grep -qE "^9001/tcp"; then
|
||||
print_success "ORPort 9001 properly exposed"
|
||||
else
|
||||
print_warning "ORPort 9001 not exposed (may be using host network)"
|
||||
fi
|
||||
|
||||
if echo "${exposed_ports}" | grep -qE "^9030/tcp"; then
|
||||
print_success "DirPort 9030 properly exposed"
|
||||
fi
|
||||
|
||||
# Check for improperly exposed internal ports
|
||||
if echo "${exposed_ports}" | grep -qE "^903[5-9]/tcp"; then
|
||||
print_error "SECURITY ISSUE: Internal metrics port exposed externally!"
|
||||
echo -e " ${RED}Fix: Ensure metrics bind to 127.0.0.1 only${NC}"
|
||||
fi
|
||||
else
|
||||
print_info "Using host network mode - checking port bindings..."
|
||||
fi
|
||||
|
||||
# Check internal service bindings
|
||||
local internal_bindings
|
||||
internal_bindings=$(sudo docker exec "${CONTAINER}" netstat -tlnp 2>/dev/null | grep -E "9035|9036|9037" || echo "")
|
||||
|
||||
if [[ -n "${internal_bindings}" ]]; then
|
||||
echo -e "\n ${BLUE}Internal services:${NC}"
|
||||
|
||||
# Validate localhost-only bindings
|
||||
while IFS= read -r line; do
|
||||
if echo "${line}" | grep -q "127.0.0.1"; then
|
||||
local port
|
||||
port=$(echo "${line}" | awk '{print $4}' | cut -d':' -f2)
|
||||
print_success "Port ${port} properly bound to localhost"
|
||||
else
|
||||
local port
|
||||
port=$(echo "${line}" | awk '{print $4}' | cut -d':' -f2)
|
||||
print_error "SECURITY ISSUE: Port ${port} not bound to localhost!"
|
||||
fi
|
||||
done <<< "${internal_bindings}"
|
||||
else
|
||||
print_info "No internal service ports detected"
|
||||
fi
|
||||
}
|
||||
|
||||
# Display recent logs
|
||||
@@ -177,6 +243,15 @@ show_orport() {
|
||||
|
||||
if [[ -n "${orport_config}" ]]; then
|
||||
echo "${orport_config}" | sed 's/^/ /'
|
||||
|
||||
# Validate port numbers
|
||||
if echo "${orport_config}" | grep -q "ORPort 9001"; then
|
||||
print_success "ORPort configured correctly (9001)"
|
||||
fi
|
||||
|
||||
if echo "${orport_config}" | grep -q "DirPort 9030"; then
|
||||
print_success "DirPort configured correctly (9030)"
|
||||
fi
|
||||
else
|
||||
print_warning "No ORPort configuration found"
|
||||
fi
|
||||
@@ -191,6 +266,13 @@ show_relay_info() {
|
||||
|
||||
if [[ -n "${relay_info}" ]]; then
|
||||
echo "${relay_info}" | sed 's/^/ /'
|
||||
|
||||
# Validate relay type
|
||||
if echo "${relay_info}" | grep -q "ExitRelay 0"; then
|
||||
print_success "Configured as guard/middle relay (not exit)"
|
||||
elif echo "${relay_info}" | grep -q "ExitRelay 1"; then
|
||||
print_warning "Configured as EXIT relay (higher legal risk)"
|
||||
fi
|
||||
else
|
||||
print_warning "No relay information found in config"
|
||||
fi
|
||||
@@ -224,10 +306,37 @@ show_resources() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Show network diagnostics
|
||||
show_network_diagnostics() {
|
||||
print_section "Network Diagnostics"
|
||||
|
||||
# Check if net-check tool is available
|
||||
if sudo docker exec "${CONTAINER}" command -v net-check &>/dev/null; then
|
||||
print_info "Running comprehensive network check..."
|
||||
sudo docker exec "${CONTAINER}" net-check 2>&1 | sed 's/^/ /'
|
||||
else
|
||||
print_info "Basic network connectivity check..."
|
||||
|
||||
# IPv4 check
|
||||
if sudo docker exec "${CONTAINER}" curl -4 -s --max-time 5 https://icanhazip.com &>/dev/null; then
|
||||
print_success "IPv4 connectivity OK"
|
||||
else
|
||||
print_warning "IPv4 connectivity issues"
|
||||
fi
|
||||
|
||||
# IPv6 check
|
||||
if sudo docker exec "${CONTAINER}" curl -6 -s --max-time 5 https://icanhazip.com &>/dev/null; then
|
||||
print_success "IPv6 connectivity OK"
|
||||
else
|
||||
print_info "IPv6 not available or configured"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Show quick help
|
||||
show_help() {
|
||||
cat << EOF
|
||||
${BLUE}Tor Relay Status Checker${NC}
|
||||
${BLUE}Tor Relay Status Checker v${VERSION}${NC}
|
||||
|
||||
Usage: $0 [container-name]
|
||||
|
||||
@@ -242,10 +351,23 @@ Examples:
|
||||
|
||||
Options:
|
||||
-h, --help Show this help message
|
||||
-v, --version Show version information
|
||||
|
||||
Security Checks:
|
||||
- Port exposure validation (9001/9030 only)
|
||||
- Internal service binding verification (127.0.0.1)
|
||||
- Bootstrap and reachability status
|
||||
- Error detection and reporting
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Show version
|
||||
show_version() {
|
||||
echo "relay-status.sh version ${VERSION}"
|
||||
echo "Part of Tor Guard Relay v1.0.2"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
# Check for help flag
|
||||
@@ -254,14 +376,21 @@ main() {
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for version flag
|
||||
if [[ "${1:-}" =~ ^(-v|--version)$ ]]; then
|
||||
show_version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect container if not specified
|
||||
if [[ -z "${CONTAINER}" ]]; then
|
||||
detect_tor_container
|
||||
fi
|
||||
|
||||
print_header "🧅 Tor Relay Status Check: ${CONTAINER}"
|
||||
print_header "🧅 Tor Relay Status Check: ${CONTAINER} (v${VERSION})"
|
||||
|
||||
check_container
|
||||
check_port_security
|
||||
show_relay_info
|
||||
show_logs
|
||||
show_bootstrap
|
||||
@@ -270,11 +399,13 @@ main() {
|
||||
show_orport
|
||||
check_errors
|
||||
show_resources
|
||||
show_network_diagnostics
|
||||
|
||||
echo
|
||||
print_header "✅ Status Check Complete"
|
||||
echo
|
||||
print_info "For live monitoring, use: sudo docker logs -f ${CONTAINER}"
|
||||
print_info "For detailed diagnostics, use: sudo docker exec ${CONTAINER} status"
|
||||
echo
|
||||
}
|
||||
|
||||
|
||||
124
tools/dashboard
124
tools/dashboard
@@ -5,12 +5,22 @@
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.0"
|
||||
VERSION="1.1.1"
|
||||
DASHBOARD_PORT="${DASHBOARD_PORT:-8080}"
|
||||
DASHBOARD_BIND="${DASHBOARD_BIND:-0.0.0.0}"
|
||||
DASHBOARD_BIND="${DASHBOARD_BIND:-127.0.0.1}" # ⚠️ CHANGED: Secure default
|
||||
ENABLE_DASHBOARD="${ENABLE_DASHBOARD:-true}"
|
||||
REFRESH_INTERVAL="${REFRESH_INTERVAL:-10}"
|
||||
MULTI_RELAY="${MULTI_RELAY:-false}"
|
||||
MAX_CONNECTIONS="${MAX_CONNECTIONS:-5}" # 🔒 NEW: Rate limiting
|
||||
|
||||
# Trap for clean exit
|
||||
trap 'cleanup' INT TERM
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Dashboard shutting down..."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
@@ -25,17 +35,26 @@ USAGE:
|
||||
|
||||
OPTIONS:
|
||||
--port PORT Dashboard port (default: 8080)
|
||||
--bind ADDR Bind address (default: 0.0.0.0)
|
||||
--bind ADDR Bind address (default: 127.0.0.1)
|
||||
--refresh SEC Auto-refresh interval (default: 10)
|
||||
--multi Enable multi-relay support
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
DASHBOARD_PORT Port to listen on
|
||||
DASHBOARD_BIND Address to bind
|
||||
DASHBOARD_BIND Address to bind (default: 127.0.0.1)
|
||||
ENABLE_DASHBOARD Enable dashboard (true/false)
|
||||
REFRESH_INTERVAL Auto-refresh in seconds
|
||||
MULTI_RELAY Multi-relay mode (true/false)
|
||||
MAX_CONNECTIONS Max concurrent connections (default: 5)
|
||||
|
||||
⚠️ SECURITY NOTICE:
|
||||
Default binding is 127.0.0.1 (localhost only).
|
||||
To expose externally, explicitly set:
|
||||
DASHBOARD_BIND=0.0.0.0
|
||||
|
||||
⚠️ WARNING: External exposure without authentication is NOT recommended!
|
||||
Use a reverse proxy (nginx/caddy) with authentication for production.
|
||||
|
||||
FEATURES:
|
||||
• Real-time relay status monitoring
|
||||
@@ -53,6 +72,13 @@ ENDPOINTS:
|
||||
http://localhost:8080/api/logs Recent logs
|
||||
|
||||
DOCKER INTEGRATION:
|
||||
# For localhost access only (secure):
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
|
||||
# For external access (use with caution):
|
||||
environment:
|
||||
- DASHBOARD_BIND=0.0.0.0
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
@@ -93,6 +119,14 @@ if [ "$ENABLE_DASHBOARD" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Security warning for external binding
|
||||
if [ "$DASHBOARD_BIND" = "0.0.0.0" ]; then
|
||||
echo "⚠️ WARNING: Dashboard is bound to 0.0.0.0 (all interfaces)"
|
||||
echo "⚠️ This exposes the dashboard without authentication!"
|
||||
echo "⚠️ Consider using a reverse proxy with authentication."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check for netcat
|
||||
if ! command -v nc > /dev/null 2>&1; then
|
||||
echo "❌ Error: netcat (nc) is required"
|
||||
@@ -100,6 +134,9 @@ if ! command -v nc > /dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Connection counter (simple rate limiting)
|
||||
CONNECTION_COUNT=0
|
||||
|
||||
# Function to generate dashboard HTML
|
||||
generate_dashboard() {
|
||||
# Get current status
|
||||
@@ -531,4 +568,81 @@ handle_api() {
|
||||
;;
|
||||
|
||||
"/api/metrics")
|
||||
CONTENT=$(/usr/local/bin/metrics 2>/dev/null || echo "# Error generating metrics
|
||||
CONTENT=$(/usr/local/bin/metrics 2>/dev/null || echo "# Error generating metrics")
|
||||
echo "HTTP/1.1 200 OK"
|
||||
echo "Content-Type: text/plain"
|
||||
echo "Cache-Control: no-cache"
|
||||
echo "Connection: close"
|
||||
echo ""
|
||||
echo "$CONTENT"
|
||||
;;
|
||||
|
||||
"/api/logs")
|
||||
CONTENT=$(/usr/local/bin/view-logs --json 2>/dev/null || echo '{"error":"Failed to get logs"}')
|
||||
echo "HTTP/1.1 200 OK"
|
||||
echo "Content-Type: application/json"
|
||||
echo "Cache-Control: no-cache"
|
||||
echo "Connection: close"
|
||||
echo ""
|
||||
echo "$CONTENT"
|
||||
;;
|
||||
|
||||
"/")
|
||||
HTML=$(generate_dashboard)
|
||||
CONTENT_LENGTH=$(echo -n "$HTML" | wc -c)
|
||||
|
||||
echo "HTTP/1.1 200 OK"
|
||||
echo "Content-Type: text/html"
|
||||
echo "Content-Length: $CONTENT_LENGTH"
|
||||
echo "Cache-Control: no-cache"
|
||||
echo "Connection: close"
|
||||
echo ""
|
||||
echo "$HTML"
|
||||
;;
|
||||
|
||||
*)
|
||||
ERROR_MSG="404 - Not Found"
|
||||
CONTENT_LENGTH=$(echo -n "$ERROR_MSG" | wc -c)
|
||||
|
||||
echo "HTTP/1.1 404 Not Found"
|
||||
echo "Content-Type: text/plain"
|
||||
echo "Content-Length: $CONTENT_LENGTH"
|
||||
echo "Connection: close"
|
||||
echo ""
|
||||
echo "$ERROR_MSG"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Start server
|
||||
echo "🎨 Starting Tor Relay Dashboard v${VERSION}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🌐 Listening on: http://$DASHBOARD_BIND:$DASHBOARD_PORT"
|
||||
echo "🔒 Max connections: $MAX_CONNECTIONS"
|
||||
echo "💡 Press Ctrl+C to stop"
|
||||
echo ""
|
||||
|
||||
# Main server loop with basic connection limiting
|
||||
while true; do
|
||||
# Simple connection limiting
|
||||
if [ "$CONNECTION_COUNT" -ge "$MAX_CONNECTIONS" ]; then
|
||||
sleep 1
|
||||
CONNECTION_COUNT=0
|
||||
fi
|
||||
|
||||
# Wait for connection and parse request
|
||||
REQUEST=$(echo "" | nc -l -p "$DASHBOARD_PORT" -s "$DASHBOARD_BIND" -w 5 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$REQUEST" ]; then
|
||||
CONNECTION_COUNT=$((CONNECTION_COUNT + 1))
|
||||
|
||||
# Extract path from request
|
||||
REQUEST_PATH=$(echo "$REQUEST" | awk '{print $2}')
|
||||
|
||||
# Log request
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $REQUEST"
|
||||
|
||||
# Generate and send response in background to avoid blocking
|
||||
(handle_api "$REQUEST_PATH" | nc -l -p "$DASHBOARD_PORT" -s "$DASHBOARD_BIND" -w 1 > /dev/null 2>&1) &
|
||||
fi
|
||||
done
|
||||
@@ -5,12 +5,22 @@
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.0"
|
||||
VERSION="1.1.1"
|
||||
METRICS_PORT="${METRICS_PORT:-9052}"
|
||||
METRICS_BIND="${METRICS_BIND:-0.0.0.0}"
|
||||
METRICS_BIND="${METRICS_BIND:-127.0.0.1}" # ⚠️ CHANGED: Secure default
|
||||
METRICS_PATH="${METRICS_PATH:-/metrics}"
|
||||
ENABLE_METRICS="${ENABLE_METRICS:-true}"
|
||||
RESPONSE_TIMEOUT="${RESPONSE_TIMEOUT:-10}"
|
||||
MAX_CONNECTIONS="${MAX_CONNECTIONS:-10}" # 🔒 NEW: Rate limiting
|
||||
|
||||
# Trap for clean exit
|
||||
trap 'cleanup' INT TERM
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Metrics HTTP server shutting down..."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
@@ -25,17 +35,30 @@ USAGE:
|
||||
|
||||
OPTIONS:
|
||||
--port PORT Listen port (default: 9052)
|
||||
--bind ADDR Bind address (default: 0.0.0.0)
|
||||
--bind ADDR Bind address (default: 127.0.0.1)
|
||||
--path PATH Metrics path (default: /metrics)
|
||||
--daemon Run as daemon
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
METRICS_PORT Port to listen on (default: 9052)
|
||||
METRICS_BIND Address to bind (default: 0.0.0.0)
|
||||
METRICS_BIND Address to bind (default: 127.0.0.1)
|
||||
METRICS_PATH URL path for metrics (default: /metrics)
|
||||
ENABLE_METRICS Enable metrics server (true/false)
|
||||
RESPONSE_TIMEOUT Response timeout in seconds
|
||||
MAX_CONNECTIONS Max concurrent connections (default: 10)
|
||||
|
||||
⚠️ SECURITY NOTICE:
|
||||
Default binding is 127.0.0.1 (localhost only).
|
||||
To expose externally for Prometheus scraping, explicitly set:
|
||||
METRICS_BIND=0.0.0.0
|
||||
|
||||
⚠️ WARNING: Metrics may contain sensitive relay information!
|
||||
Recommendations for production:
|
||||
1. Use network-level access controls (firewall rules)
|
||||
2. Deploy Prometheus in same network/VPN
|
||||
3. Use TLS termination proxy (nginx with client certs)
|
||||
4. Never expose directly to public internet
|
||||
|
||||
ENDPOINTS:
|
||||
http://localhost:9052/metrics Prometheus metrics
|
||||
@@ -51,7 +74,18 @@ PROMETHEUS CONFIG:
|
||||
scrape_interval: 30s
|
||||
|
||||
DOCKER INTEGRATION:
|
||||
# Expose in docker-compose.yml
|
||||
# For localhost access only (secure):
|
||||
ports:
|
||||
- "127.0.0.1:9052:9052"
|
||||
|
||||
# For Prometheus in same Docker network:
|
||||
networks:
|
||||
- monitoring
|
||||
# No port exposure needed!
|
||||
|
||||
# For external Prometheus (use with caution):
|
||||
environment:
|
||||
- METRICS_BIND=0.0.0.0
|
||||
ports:
|
||||
- "9052:9052"
|
||||
|
||||
@@ -92,6 +126,14 @@ if [ "$ENABLE_METRICS" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Security warning for external binding
|
||||
if [ "$METRICS_BIND" = "0.0.0.0" ]; then
|
||||
echo "⚠️ WARNING: Metrics server is bound to 0.0.0.0 (all interfaces)"
|
||||
echo "⚠️ Relay metrics may contain sensitive information!"
|
||||
echo "⚠️ Ensure proper firewall rules or use a secure network."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check for netcat
|
||||
if ! command -v nc > /dev/null 2>&1; then
|
||||
echo "❌ Error: netcat (nc) is required but not installed"
|
||||
@@ -99,6 +141,10 @@ if ! command -v nc > /dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Connection counter for basic rate limiting
|
||||
CONNECTION_COUNT=0
|
||||
LAST_RESET=$(date +%s)
|
||||
|
||||
# Function to generate HTTP response
|
||||
generate_response() {
|
||||
REQUEST_PATH="$1"
|
||||
@@ -115,6 +161,7 @@ Content-Type: text/plain; version=0.0.4
|
||||
Content-Length: $CONTENT_LENGTH
|
||||
Cache-Control: no-cache
|
||||
Connection: close
|
||||
X-Content-Type-Options: nosniff
|
||||
|
||||
$METRICS_OUTPUT
|
||||
EOF
|
||||
@@ -142,6 +189,8 @@ EOF
|
||||
<html>
|
||||
<head>
|
||||
<title>Tor Relay Metrics</title>
|
||||
<meta charset=\"utf-8\">
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 40px; background: #f5f5f5; }
|
||||
h1 { color: #7d4698; }
|
||||
@@ -149,12 +198,17 @@ EOF
|
||||
.endpoint { background: #f0f0f0; padding: 10px; margin: 10px 0; border-radius: 4px; }
|
||||
a { color: #7d4698; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.ok { color: green; }
|
||||
.loading { color: orange; }
|
||||
.warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧅 Tor Relay Metrics Server</h1>
|
||||
|
||||
<div class=\"warning\">
|
||||
<strong>⚠️ Security Notice:</strong> This server exposes relay metrics.
|
||||
Ensure it's only accessible from trusted networks (Prometheus, monitoring systems).
|
||||
</div>
|
||||
|
||||
<div class=\"status\">
|
||||
<h2>Available Endpoints:</h2>
|
||||
<div class=\"endpoint\">
|
||||
@@ -167,11 +221,25 @@ EOF
|
||||
🏠 <a href=\"/\">/</a> - This status page
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=\"status\">
|
||||
<h2>Configuration:</h2>
|
||||
<p>Port: $METRICS_PORT</p>
|
||||
<p>Bind: $METRICS_BIND</p>
|
||||
<p>Version: $VERSION</p>
|
||||
<p><strong>Bind Address:</strong> $METRICS_BIND</p>
|
||||
<p><strong>Port:</strong> $METRICS_PORT</p>
|
||||
<p><strong>Version:</strong> $VERSION</p>
|
||||
<p><strong>Rate Limit:</strong> $MAX_CONNECTIONS connections/window</p>
|
||||
</div>
|
||||
|
||||
<div class=\"status\">
|
||||
<h2>Integration:</h2>
|
||||
<p>Add to your <code>prometheus.yml</code>:</p>
|
||||
<pre style=\"background: #f0f0f0; padding: 15px; border-radius: 4px; overflow-x: auto;\">
|
||||
scrape_configs:
|
||||
- job_name: 'tor-relay'
|
||||
static_configs:
|
||||
- targets: ['$METRICS_BIND:$METRICS_PORT']
|
||||
metrics_path: '$METRICS_PATH'
|
||||
scrape_interval: 30s</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>"
|
||||
@@ -180,7 +248,7 @@ EOF
|
||||
|
||||
cat << EOF
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/html
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: $CONTENT_LENGTH
|
||||
Cache-Control: no-cache
|
||||
Connection: close
|
||||
@@ -206,46 +274,43 @@ EOF
|
||||
esac
|
||||
}
|
||||
|
||||
# Signal handler for clean shutdown
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Shutting down metrics HTTP server..."
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup INT TERM
|
||||
|
||||
# Start server
|
||||
echo "📊 Starting Tor Relay Metrics HTTP Server v${VERSION}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🌐 Listening on: http://$METRICS_BIND:$METRICS_PORT"
|
||||
echo "📍 Metrics path: $METRICS_PATH"
|
||||
echo "🔒 Max connections: $MAX_CONNECTIONS/window"
|
||||
echo "💡 Press Ctrl+C to stop"
|
||||
echo ""
|
||||
|
||||
# Main server loop
|
||||
if [ "$DAEMON_MODE" = "true" ]; then
|
||||
# Run in background
|
||||
while true; do
|
||||
echo -e "$(generate_response "$METRICS_PATH")" | nc -l -p "$METRICS_PORT" -w "$RESPONSE_TIMEOUT" > /dev/null 2>&1 &
|
||||
wait
|
||||
done &
|
||||
echo "✅ Server running in daemon mode (PID: $!)"
|
||||
else
|
||||
# Run in foreground
|
||||
while true; do
|
||||
# Wait for connection and parse request
|
||||
REQUEST=$(echo -e "HTTP/1.1 200 OK\r\n\r\n" | nc -l -p "$METRICS_PORT" -w "$RESPONSE_TIMEOUT" 2>/dev/null | head -1)
|
||||
# Main server loop with connection limiting
|
||||
while true; do
|
||||
# Reset counter every 60 seconds
|
||||
CURRENT_TIME=$(date +%s)
|
||||
if [ $((CURRENT_TIME - LAST_RESET)) -ge 60 ]; then
|
||||
CONNECTION_COUNT=0
|
||||
LAST_RESET=$CURRENT_TIME
|
||||
fi
|
||||
|
||||
# Basic rate limiting
|
||||
if [ "$CONNECTION_COUNT" -ge "$MAX_CONNECTIONS" ]; then
|
||||
sleep 1
|
||||
continue
|
||||
fi
|
||||
|
||||
# Wait for connection and parse request
|
||||
REQUEST=$(echo "" | nc -l -p "$METRICS_PORT" -s "$METRICS_BIND" -w "$RESPONSE_TIMEOUT" 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$REQUEST" ]; then
|
||||
CONNECTION_COUNT=$((CONNECTION_COUNT + 1))
|
||||
|
||||
if [ -n "$REQUEST" ]; then
|
||||
# Extract path from request
|
||||
REQUEST_PATH=$(echo "$REQUEST" | awk '{print $2}')
|
||||
|
||||
# Log request
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $REQUEST"
|
||||
|
||||
# Generate and send response
|
||||
generate_response "$REQUEST_PATH" | nc -l -p "$METRICS_PORT" -w 1 > /dev/null 2>&1 &
|
||||
fi
|
||||
done
|
||||
fi
|
||||
# Extract path from request
|
||||
REQUEST_PATH=$(echo "$REQUEST" | awk '{print $2}')
|
||||
|
||||
# Log request (without sensitive info)
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $REQUEST_PATH ($CONNECTION_COUNT/$MAX_CONNECTIONS)"
|
||||
|
||||
# Generate and send response in background
|
||||
(generate_response "$REQUEST_PATH" | nc -l -p "$METRICS_PORT" -s "$METRICS_BIND" -w 1 > /dev/null 2>&1) &
|
||||
fi
|
||||
done
|
||||
168
tools/setup
168
tools/setup
@@ -5,7 +5,7 @@
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.0"
|
||||
VERSION="1.1.1"
|
||||
CONFIG_FILE="${CONFIG_FILE:-/etc/tor/torrc}"
|
||||
RELAY_TYPE="${RELAY_TYPE:-guard}"
|
||||
AUTO_MODE="${AUTO_MODE:-false}"
|
||||
@@ -14,6 +14,7 @@ DEFAULT_CONTACT="${DEFAULT_CONTACT:-admin@example.com}"
|
||||
DEFAULT_ORPORT="${DEFAULT_ORPORT:-9001}"
|
||||
DEFAULT_DIRPORT="${DEFAULT_DIRPORT:-9030}"
|
||||
DEFAULT_BANDWIDTH="${DEFAULT_BANDWIDTH:-1024}"
|
||||
CREATE_BACKUP="${CREATE_BACKUP:-true}" # 🔒 NEW: Backup control
|
||||
|
||||
# Colors for terminal output
|
||||
RED='\033[0;31m'
|
||||
@@ -23,6 +24,15 @@ BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Trap for clean exit
|
||||
trap 'cleanup' INT TERM
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo -e "${YELLOW}Setup cancelled by user${NC}"
|
||||
exit 130
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
@@ -38,6 +48,7 @@ OPTIONS:
|
||||
--auto Use defaults for all prompts
|
||||
--config FILE Config file path (default: /etc/tor/torrc)
|
||||
--type TYPE Relay type: guard|exit|bridge
|
||||
--no-backup Skip backup creation (not recommended)
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
@@ -49,12 +60,19 @@ ENVIRONMENT VARIABLES:
|
||||
DEFAULT_DIRPORT Default DirPort
|
||||
DEFAULT_BANDWIDTH Default bandwidth in KB/s
|
||||
AUTO_MODE Skip prompts and use defaults
|
||||
CREATE_BACKUP Create backup before overwriting (default: true)
|
||||
|
||||
RELAY TYPES:
|
||||
guard Middle/Guard relay (recommended for beginners)
|
||||
exit Exit relay (requires careful consideration)
|
||||
bridge Bridge relay (helps censored users)
|
||||
|
||||
SAFETY FEATURES:
|
||||
🔒 Automatic backup creation before overwriting existing config
|
||||
🔒 Backup stored as: [config].backup.[timestamp]
|
||||
🔒 Validation before writing
|
||||
🔒 Safe cancellation with Ctrl+C
|
||||
|
||||
WIZARD STEPS:
|
||||
1. Relay nickname selection
|
||||
2. Contact information
|
||||
@@ -67,6 +85,7 @@ EXAMPLES:
|
||||
setup # Interactive wizard
|
||||
setup --auto # Auto-configure with defaults
|
||||
setup --type bridge # Configure as bridge relay
|
||||
setup --no-backup # Skip backup (not recommended)
|
||||
|
||||
OUTPUT:
|
||||
Creates a complete torrc configuration file ready
|
||||
@@ -87,6 +106,7 @@ EOF
|
||||
RELAY_TYPE="$1"
|
||||
shift
|
||||
;;
|
||||
--no-backup) CREATE_BACKUP="false" ;;
|
||||
-*)
|
||||
echo "❌ Unknown option: $arg"
|
||||
echo "💡 Use --help for usage information"
|
||||
@@ -124,6 +144,47 @@ validate_bandwidth() {
|
||||
[ "$1" -ge 256 ] 2>/dev/null
|
||||
}
|
||||
|
||||
# 🔒 NEW: Backup function
|
||||
create_config_backup() {
|
||||
if [ -f "$CONFIG_FILE" ] && [ "$CREATE_BACKUP" = "true" ]; then
|
||||
BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="${CONFIG_FILE}.backup.${BACKUP_TIMESTAMP}"
|
||||
|
||||
echo -e "${BLUE}📦 Creating backup...${NC}"
|
||||
|
||||
if cp "$CONFIG_FILE" "$BACKUP_FILE" 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ Backup created: $BACKUP_FILE${NC}"
|
||||
|
||||
# Keep only last 5 backups
|
||||
BACKUP_COUNT=$(ls -1 "${CONFIG_FILE}.backup."* 2>/dev/null | wc -l)
|
||||
if [ "$BACKUP_COUNT" -gt 5 ]; then
|
||||
echo -e "${YELLOW}🧹 Cleaning old backups (keeping last 5)...${NC}"
|
||||
ls -1t "${CONFIG_FILE}.backup."* | tail -n +6 | xargs rm -f 2>/dev/null || true
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Warning: Could not create backup${NC}"
|
||||
echo -e "${YELLOW} Proceeding anyway...${NC}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 🔒 NEW: Restore function (if setup fails)
|
||||
restore_from_backup() {
|
||||
LATEST_BACKUP=$(ls -1t "${CONFIG_FILE}.backup."* 2>/dev/null | head -1)
|
||||
if [ -n "$LATEST_BACKUP" ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}❌ Setup failed. Restoring from backup...${NC}"
|
||||
if cp "$LATEST_BACKUP" "$CONFIG_FILE" 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ Configuration restored from: $LATEST_BACKUP${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Failed to restore backup${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main setup wizard
|
||||
clear
|
||||
cat << EOF
|
||||
@@ -138,9 +199,33 @@ ${PURPLE}╔══════════════════════
|
||||
This wizard will help you configure a Tor relay with optimal
|
||||
settings for your network and preferences.
|
||||
|
||||
Press Ctrl+C at any time to cancel.
|
||||
Press Ctrl+C at any time to cancel safely.
|
||||
EOF
|
||||
|
||||
# Check if config exists
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}⚠️ Existing configuration found at:${NC}"
|
||||
echo -e "${YELLOW} $CONFIG_FILE${NC}"
|
||||
echo ""
|
||||
|
||||
if [ "$CREATE_BACKUP" = "true" ]; then
|
||||
echo -e "${GREEN}🔒 A backup will be created before making changes.${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠️ Backup creation is disabled!${NC}"
|
||||
fi
|
||||
|
||||
if [ "$AUTO_MODE" != "true" ]; then
|
||||
echo ""
|
||||
printf "Continue and overwrite existing config? [y/N]: "
|
||||
read CONFIRM
|
||||
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
|
||||
echo -e "${YELLOW}Setup cancelled.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 1: Nickname
|
||||
print_header "Step 1: Relay Nickname"
|
||||
print_step "1" "Choose a nickname for your relay"
|
||||
@@ -297,8 +382,11 @@ print_header "Step 6: Generating Configuration"
|
||||
print_step "6" "Creating torrc file"
|
||||
echo ""
|
||||
|
||||
# 🔒 Create backup before overwriting
|
||||
create_config_backup
|
||||
|
||||
# Create configuration
|
||||
cat > "$CONFIG_FILE" << EOF
|
||||
if ! cat > "$CONFIG_FILE" << EOF
|
||||
# Tor Relay Configuration
|
||||
# Generated by Tor-Guard-Relay Setup Wizard v${VERSION}
|
||||
# Date: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
@@ -313,76 +401,4 @@ DirPort $DIRPORT
|
||||
|
||||
# Bandwidth Limits
|
||||
RelayBandwidthRate $BANDWIDTH KB
|
||||
RelayBandwidthBurst $((BANDWIDTH * 2)) KB
|
||||
|
||||
# Relay Type Configuration
|
||||
EOF
|
||||
|
||||
case "$RELAY_TYPE" in
|
||||
exit)
|
||||
cat >> "$CONFIG_FILE" << EOF
|
||||
ExitRelay 1
|
||||
ExitPolicy accept *:80 # HTTP
|
||||
ExitPolicy accept *:443 # HTTPS
|
||||
ExitPolicy accept *:6667 # IRC
|
||||
ExitPolicy accept *:22 # SSH
|
||||
ExitPolicy reject *:* # Reject everything else
|
||||
EOF
|
||||
;;
|
||||
bridge)
|
||||
cat >> "$CONFIG_FILE" << EOF
|
||||
BridgeRelay 1
|
||||
PublishServerDescriptor bridge
|
||||
ExitRelay 0
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
cat >> "$CONFIG_FILE" << EOF
|
||||
ExitRelay 0
|
||||
ExitPolicy reject *:*
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
# Add common settings
|
||||
cat >> "$CONFIG_FILE" << EOF
|
||||
|
||||
# Logging
|
||||
Log notice file /var/log/tor/notices.log
|
||||
Log notice syslog
|
||||
|
||||
# Data Directory
|
||||
DataDirectory /var/lib/tor
|
||||
|
||||
# Performance
|
||||
NumCPUs 0 # Use all available CPUs
|
||||
HardwareAccel 1
|
||||
|
||||
# Security
|
||||
NoExec 1
|
||||
EOF
|
||||
|
||||
# Validate configuration
|
||||
echo -e "${GREEN}✓${NC} Configuration generated successfully!"
|
||||
echo ""
|
||||
echo "📋 Configuration Summary:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Nickname: $NICKNAME"
|
||||
echo " Contact: $CONTACT"
|
||||
echo " ORPort: $ORPORT"
|
||||
echo " DirPort: $DIRPORT"
|
||||
echo " Bandwidth: $BANDWIDTH KB/s"
|
||||
echo " Type: $RELAY_TYPE"
|
||||
echo " Config: $CONFIG_FILE"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Setup complete!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review the configuration: cat $CONFIG_FILE"
|
||||
echo " 2. Start your relay: tor -f $CONFIG_FILE"
|
||||
echo " 3. Monitor status: /usr/local/bin/status"
|
||||
echo " 4. Check metrics: /usr/local/bin/metrics"
|
||||
echo ""
|
||||
echo "🌐 Your relay will appear on Tor Metrics after ~3 hours:"
|
||||
echo " https://metrics.torproject.org/rs.html"
|
||||
RelayBandwidthBurst $((
|
||||
Reference in New Issue
Block a user