mirror of
https://github.com/actions/cache.git
synced 2026-01-30 16:04:23 +08:00
- Switch from ubuntu/squid to wernight/squid which allows all HTTPS CONNECT - Fix verification tests to explicitly use -x flag to prove proxy works - Tests now verify: 1. Proxy accepts and forwards requests (using curl -x) 2. Direct blob storage access is blocked by iptables 3. Blob storage access through proxy succeeds The cache action should now fail because it doesn't use the proxy, not because the proxy rejects the connection.
449 lines
18 KiB
YAML
449 lines
18 KiB
YAML
name: Tests
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- releases/**
|
|
push:
|
|
branches:
|
|
- main
|
|
- releases/**
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
# Build and unit test
|
|
build:
|
|
strategy:
|
|
matrix:
|
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
fail-fast: false
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
- name: Setup Node.js 24.x
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24.x
|
|
cache: npm
|
|
- run: npm ci
|
|
- name: Prettier Format Check
|
|
run: npm run format-check
|
|
- name: ESLint Check
|
|
run: npm run lint
|
|
- name: Build & Test
|
|
run: npm run test
|
|
|
|
# End to end save and restore
|
|
test-save:
|
|
strategy:
|
|
matrix:
|
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
fail-fast: false
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
- name: Generate files in working directory
|
|
shell: bash
|
|
run: __tests__/create-cache-files.sh ${{ runner.os }} test-cache
|
|
- name: Generate files outside working directory
|
|
shell: bash
|
|
run: __tests__/create-cache-files.sh ${{ runner.os }} ~/test-cache
|
|
- name: Save cache
|
|
uses: ./
|
|
with:
|
|
key: test-${{ runner.os }}-${{ github.run_id }}
|
|
path: |
|
|
test-cache
|
|
~/test-cache
|
|
|
|
test-restore:
|
|
needs: test-save
|
|
strategy:
|
|
matrix:
|
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
fail-fast: false
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
- name: Restore cache
|
|
uses: ./
|
|
with:
|
|
key: test-${{ runner.os }}-${{ github.run_id }}
|
|
path: |
|
|
test-cache
|
|
~/test-cache
|
|
- name: Verify cache files in working directory
|
|
shell: bash
|
|
run: __tests__/verify-cache-files.sh ${{ runner.os }} test-cache
|
|
- name: Verify cache files outside working directory
|
|
shell: bash
|
|
run: __tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache
|
|
|
|
# End to end with proxy
|
|
test-proxy-save:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:latest
|
|
options: --privileged
|
|
services:
|
|
squid-proxy:
|
|
image: wernight/squid
|
|
ports:
|
|
- 3128:3128
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
- name: Install dependencies
|
|
run: |
|
|
apt-get update
|
|
apt-get install -y iptables dnsutils curl jq ipset
|
|
- name: Fetch GitHub meta and configure firewall
|
|
run: |
|
|
# Fetch GitHub meta API to get all IP ranges
|
|
echo "Fetching GitHub meta API..."
|
|
curl -sS https://api.github.com/meta > /tmp/github-meta.json
|
|
|
|
# Wait for squid-proxy service to be resolvable and accepting connections
|
|
echo "Waiting for squid-proxy service..."
|
|
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
|
|
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
|
|
if [ -n "$PROXY_IP" ]; then
|
|
echo "squid-proxy resolved to: $PROXY_IP"
|
|
# Test that proxy is actually accepting connections
|
|
if curl --connect-timeout 2 --max-time 5 -x http://squid-proxy:3128 -sS https://api.github.com/zen 2>/dev/null; then
|
|
echo "Proxy is working!"
|
|
break
|
|
else
|
|
echo "Attempt $i: Proxy resolved but not ready yet, waiting..."
|
|
fi
|
|
else
|
|
echo "Attempt $i: squid-proxy not resolvable yet, waiting..."
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
if [ -z "$PROXY_IP" ]; then
|
|
echo "ERROR: Could not resolve squid-proxy after 15 attempts"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify proxy works before locking down firewall
|
|
echo "Final proxy connectivity test..."
|
|
if ! curl --connect-timeout 5 --max-time 10 -x http://squid-proxy:3128 -sS https://api.github.com/zen; then
|
|
echo "ERROR: Proxy is not working properly"
|
|
exit 1
|
|
fi
|
|
echo "Proxy verified working!"
|
|
|
|
# Allow established connections
|
|
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
|
|
# Allow loopback
|
|
iptables -A OUTPUT -o lo -j ACCEPT
|
|
|
|
# Allow connections to the proxy
|
|
iptables -A OUTPUT -d $PROXY_IP -p tcp --dport 3128 -j ACCEPT
|
|
|
|
# Allow DNS
|
|
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
|
|
|
# Create ipset for GitHub IPs (more efficient than individual rules)
|
|
ipset create github-ips hash:net
|
|
|
|
# Add all GitHub IP ranges from meta API (hooks, web, api, git, actions, etc.)
|
|
# EXCLUDING blob storage which must go through proxy
|
|
for category in hooks web api git pages importer actions actions_macos codespaces copilot; do
|
|
echo "Adding IPs for category: $category"
|
|
jq -r ".${category}[]? // empty" /tmp/github-meta.json 2>/dev/null | while read cidr; do
|
|
# Skip IPv6 for now (iptables vs ip6tables) - use case for POSIX compatibility
|
|
case "$cidr" in
|
|
*:*) ;; # IPv6, skip
|
|
*) ipset add github-ips "$cidr" 2>/dev/null || true ;;
|
|
esac
|
|
done
|
|
done
|
|
|
|
# Allow all GitHub IPs
|
|
iptables -A OUTPUT -m set --match-set github-ips dst -p tcp --dport 443 -j ACCEPT
|
|
iptables -A OUTPUT -m set --match-set github-ips dst -p tcp --dport 80 -j ACCEPT
|
|
|
|
# CRITICAL: Block direct access to blob storage and results-receiver
|
|
# These MUST go through the proxy for cache operations
|
|
echo "Blocking direct access to cache-critical endpoints..."
|
|
|
|
# Block results-receiver.actions.githubusercontent.com
|
|
for ip in $(getent ahosts "results-receiver.actions.githubusercontent.com" 2>/dev/null | awk '{print $1}' | sort -u); do
|
|
echo "Blocking direct access to results-receiver: $ip"
|
|
iptables -I OUTPUT 1 -d "$ip" -p tcp --dport 443 -j REJECT
|
|
done
|
|
|
|
# Block blob.core.windows.net (Azure blob storage used for cache)
|
|
for host in productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net; do
|
|
for ip in $(getent ahosts "$host" 2>/dev/null | awk '{print $1}' | sort -u); do
|
|
echo "Blocking direct access to blob storage ($host): $ip"
|
|
iptables -I OUTPUT 1 -d "$ip" -p tcp --dport 443 -j REJECT
|
|
done
|
|
done
|
|
|
|
# Block all other outbound HTTP/HTTPS traffic
|
|
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
|
|
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
|
|
|
|
echo "iptables rules applied:"
|
|
iptables -L OUTPUT -n -v
|
|
echo ""
|
|
echo "ipset github-ips contains $(ipset list github-ips | grep -c '^[0-9]') entries"
|
|
- name: Verify proxy enforcement
|
|
run: |
|
|
echo "=== Testing proxy enforcement ==="
|
|
|
|
# Test 1: Verify proxy is working by explicitly using it
|
|
echo "Test 1: Connection through proxy (should SUCCEED)"
|
|
if curl --connect-timeout 10 --max-time 15 -x http://squid-proxy:3128 -sS -o /dev/null -w "%{http_code}" https://api.github.com/zen; then
|
|
echo ""
|
|
echo "✓ Proxy connection works"
|
|
else
|
|
echo "✗ ERROR: Proxy is not working!"
|
|
exit 1
|
|
fi
|
|
|
|
# Test 2: Direct connection to blob storage should FAIL (blocked by iptables)
|
|
echo ""
|
|
echo "Test 2: Direct connection to blob storage (should FAIL - blocked by iptables)"
|
|
if curl --connect-timeout 5 --max-time 10 --noproxy '*' -sS https://productionresultssa0.blob.core.windows.net 2>/dev/null; then
|
|
echo "✗ ERROR: Direct blob storage connection succeeded but should have been blocked!"
|
|
exit 1
|
|
else
|
|
echo "✓ Direct blob storage correctly blocked by iptables"
|
|
fi
|
|
|
|
# Test 3: Connection to blob storage THROUGH proxy should work
|
|
echo ""
|
|
echo "Test 3: Connection through proxy to blob storage (should SUCCEED)"
|
|
HTTP_CODE=$(curl --connect-timeout 10 --max-time 15 -x http://squid-proxy:3128 -sS -o /dev/null -w "%{http_code}" https://productionresultssa0.blob.core.windows.net 2>&1) || true
|
|
echo "HTTP response code: $HTTP_CODE"
|
|
if [ "$HTTP_CODE" = "400" ] || [ "$HTTP_CODE" = "409" ] || [ "$HTTP_CODE" = "200" ]; then
|
|
echo "✓ Proxy successfully forwarded request to blob storage (got HTTP $HTTP_CODE)"
|
|
else
|
|
echo "✗ ERROR: Proxy failed to forward request (got: $HTTP_CODE)"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== All proxy enforcement tests passed ==="
|
|
echo "The proxy is working. If cache operations fail, it's because the action doesn't use the proxy."
|
|
- name: Generate files
|
|
run: __tests__/create-cache-files.sh proxy test-cache
|
|
- name: Save cache
|
|
env:
|
|
http_proxy: http://squid-proxy:3128
|
|
https_proxy: http://squid-proxy:3128
|
|
uses: ./
|
|
with:
|
|
key: test-proxy-${{ github.run_id }}
|
|
path: test-cache
|
|
- name: Verify proxy setup
|
|
run: |
|
|
echo "## 🔒 Proxy Integration Test - Cache Save" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "### ✅ Test Configuration" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Proxy**: squid-proxy:3128" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Firewall**: iptables blocking direct access to cache endpoints" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Test**: Cache save operation completed successfully through proxy" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "If the cache save step succeeded, it means:" >> $GITHUB_STEP_SUMMARY
|
|
echo "1. Direct access to results-receiver.actions.githubusercontent.com was blocked" >> $GITHUB_STEP_SUMMARY
|
|
echo "2. Direct access to *.blob.core.windows.net was blocked" >> $GITHUB_STEP_SUMMARY
|
|
echo "3. Cache operations were routed through the squid proxy" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "✅ **SUCCESS**: Proxy integration test passed!" >> $GITHUB_STEP_SUMMARY
|
|
|
|
test-proxy-restore:
|
|
needs: test-proxy-save
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:latest
|
|
options: --privileged
|
|
services:
|
|
squid-proxy:
|
|
image: wernight/squid
|
|
ports:
|
|
- 3128:3128
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
- name: Install dependencies
|
|
run: |
|
|
apt-get update
|
|
apt-get install -y iptables dnsutils curl jq ipset
|
|
- name: Fetch GitHub meta and configure firewall
|
|
run: |
|
|
# Fetch GitHub meta API to get all IP ranges
|
|
echo "Fetching GitHub meta API..."
|
|
curl -sS https://api.github.com/meta > /tmp/github-meta.json
|
|
|
|
# Wait for squid-proxy service to be resolvable and accepting connections
|
|
echo "Waiting for squid-proxy service..."
|
|
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
|
|
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
|
|
if [ -n "$PROXY_IP" ]; then
|
|
echo "squid-proxy resolved to: $PROXY_IP"
|
|
# Test that proxy is actually accepting connections
|
|
if curl --connect-timeout 2 --max-time 5 -x http://squid-proxy:3128 -sS https://api.github.com/zen 2>/dev/null; then
|
|
echo "Proxy is working!"
|
|
break
|
|
else
|
|
echo "Attempt $i: Proxy resolved but not ready yet, waiting..."
|
|
fi
|
|
else
|
|
echo "Attempt $i: squid-proxy not resolvable yet, waiting..."
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
if [ -z "$PROXY_IP" ]; then
|
|
echo "ERROR: Could not resolve squid-proxy after 15 attempts"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify proxy works before locking down firewall
|
|
echo "Final proxy connectivity test..."
|
|
if ! curl --connect-timeout 5 --max-time 10 -x http://squid-proxy:3128 -sS https://api.github.com/zen; then
|
|
echo "ERROR: Proxy is not working properly"
|
|
exit 1
|
|
fi
|
|
echo "Proxy verified working!"
|
|
|
|
# Allow established connections
|
|
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
|
|
# Allow loopback
|
|
iptables -A OUTPUT -o lo -j ACCEPT
|
|
|
|
# Allow connections to the proxy
|
|
iptables -A OUTPUT -d $PROXY_IP -p tcp --dport 3128 -j ACCEPT
|
|
|
|
# Allow DNS
|
|
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
|
|
|
# Create ipset for GitHub IPs (more efficient than individual rules)
|
|
ipset create github-ips hash:net
|
|
|
|
# Add all GitHub IP ranges from meta API (hooks, web, api, git, actions, etc.)
|
|
# EXCLUDING blob storage which must go through proxy
|
|
for category in hooks web api git pages importer actions actions_macos codespaces copilot; do
|
|
echo "Adding IPs for category: $category"
|
|
jq -r ".${category}[]? // empty" /tmp/github-meta.json 2>/dev/null | while read cidr; do
|
|
# Skip IPv6 for now (iptables vs ip6tables) - use case for POSIX compatibility
|
|
case "$cidr" in
|
|
*:*) ;; # IPv6, skip
|
|
*) ipset add github-ips "$cidr" 2>/dev/null || true ;;
|
|
esac
|
|
done
|
|
done
|
|
|
|
# Allow all GitHub IPs
|
|
iptables -A OUTPUT -m set --match-set github-ips dst -p tcp --dport 443 -j ACCEPT
|
|
iptables -A OUTPUT -m set --match-set github-ips dst -p tcp --dport 80 -j ACCEPT
|
|
|
|
# CRITICAL: Block direct access to blob storage and results-receiver
|
|
# These MUST go through the proxy for cache operations
|
|
echo "Blocking direct access to cache-critical endpoints..."
|
|
|
|
# Block results-receiver.actions.githubusercontent.com
|
|
for ip in $(getent ahosts "results-receiver.actions.githubusercontent.com" 2>/dev/null | awk '{print $1}' | sort -u); do
|
|
echo "Blocking direct access to results-receiver: $ip"
|
|
iptables -I OUTPUT 1 -d "$ip" -p tcp --dport 443 -j REJECT
|
|
done
|
|
|
|
# Block blob.core.windows.net (Azure blob storage used for cache)
|
|
for host in productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net; do
|
|
for ip in $(getent ahosts "$host" 2>/dev/null | awk '{print $1}' | sort -u); do
|
|
echo "Blocking direct access to blob storage ($host): $ip"
|
|
iptables -I OUTPUT 1 -d "$ip" -p tcp --dport 443 -j REJECT
|
|
done
|
|
done
|
|
|
|
# Block all other outbound HTTP/HTTPS traffic
|
|
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
|
|
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
|
|
|
|
echo "iptables rules applied:"
|
|
iptables -L OUTPUT -n -v
|
|
echo ""
|
|
echo "ipset github-ips contains $(ipset list github-ips | grep -c '^[0-9]') entries"
|
|
- name: Verify proxy enforcement
|
|
run: |
|
|
echo "=== Testing proxy enforcement ==="
|
|
|
|
# Test 1: Verify proxy is working by explicitly using it
|
|
echo "Test 1: Connection through proxy (should SUCCEED)"
|
|
if curl --connect-timeout 10 --max-time 15 -x http://squid-proxy:3128 -sS -o /dev/null -w "%{http_code}" https://api.github.com/zen; then
|
|
echo ""
|
|
echo "✓ Proxy connection works"
|
|
else
|
|
echo "✗ ERROR: Proxy is not working!"
|
|
exit 1
|
|
fi
|
|
|
|
# Test 2: Direct connection to blob storage should FAIL (blocked by iptables)
|
|
echo ""
|
|
echo "Test 2: Direct connection to blob storage (should FAIL - blocked by iptables)"
|
|
if curl --connect-timeout 5 --max-time 10 --noproxy '*' -sS https://productionresultssa0.blob.core.windows.net 2>/dev/null; then
|
|
echo "✗ ERROR: Direct blob storage connection succeeded but should have been blocked!"
|
|
exit 1
|
|
else
|
|
echo "✓ Direct blob storage correctly blocked by iptables"
|
|
fi
|
|
|
|
# Test 3: Connection to blob storage THROUGH proxy should work
|
|
echo ""
|
|
echo "Test 3: Connection through proxy to blob storage (should SUCCEED)"
|
|
HTTP_CODE=$(curl --connect-timeout 10 --max-time 15 -x http://squid-proxy:3128 -sS -o /dev/null -w "%{http_code}" https://productionresultssa0.blob.core.windows.net 2>&1) || true
|
|
echo "HTTP response code: $HTTP_CODE"
|
|
if [ "$HTTP_CODE" = "400" ] || [ "$HTTP_CODE" = "409" ] || [ "$HTTP_CODE" = "200" ]; then
|
|
echo "✓ Proxy successfully forwarded request to blob storage (got HTTP $HTTP_CODE)"
|
|
else
|
|
echo "✗ ERROR: Proxy failed to forward request (got: $HTTP_CODE)"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== All proxy enforcement tests passed ==="
|
|
echo "The proxy is working. If cache operations fail, it's because the action doesn't use the proxy."
|
|
- name: Restore cache
|
|
env:
|
|
http_proxy: http://squid-proxy:3128
|
|
https_proxy: http://squid-proxy:3128
|
|
uses: ./
|
|
with:
|
|
key: test-proxy-${{ github.run_id }}
|
|
path: test-cache
|
|
- name: Verify proxy setup
|
|
run: |
|
|
echo "## 🔒 Proxy Integration Test - Cache Restore" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "### ✅ Test Configuration" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Proxy**: squid-proxy:3128" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Firewall**: iptables blocking direct access to cache endpoints" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Test**: Cache restore operation completed successfully through proxy" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "If the cache restore step succeeded, it means:" >> $GITHUB_STEP_SUMMARY
|
|
echo "1. Direct access to results-receiver.actions.githubusercontent.com was blocked" >> $GITHUB_STEP_SUMMARY
|
|
echo "2. Direct access to *.blob.core.windows.net was blocked" >> $GITHUB_STEP_SUMMARY
|
|
echo "3. Cache operations were routed through the squid proxy" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "✅ **SUCCESS**: Proxy integration test passed!" >> $GITHUB_STEP_SUMMARY
|
|
- name: Verify cache
|
|
run: __tests__/verify-cache-files.sh proxy test-cache
|