feat(lab-02): complete Phase 3 - Network & VPC lab
Implement Lab 02 with Docker bridge networks simulating VPC/Subnets. Test Infrastructure (RED phase): - 6 bash test scripts for network creation, isolation, INF-02 compliance - Fail-fast orchestration with run-all-tests.sh - Quick validation script for development Documentation (Diátaxis framework): - 3 tutorials: VPC creation, container deployment, isolation verification - 4 how-to guides: create network, inspect config, test isolation, cleanup - 3 reference docs: Docker network commands, Compose syntax, VPC mapping - 1 explanation: Docker ↔ VPC parallels (PARA-01/02/03/04) Infrastructure (GREEN phase): - docker-compose.yml with VPC networks (10.0.1.0/24, 10.0.2.0/24) - 5 services: web, app, db, test-public, test-private - INF-02 compliant: 127.0.0.1 bindings only, no 0.0.0.0 - Private network with --internal flag - Multi-homed app container (public + private networks) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
272
labs/lab-02-network/tests/03-inf02-compliance-test.sh
Executable file
272
labs/lab-02-network/tests/03-inf02-compliance-test.sh
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/bin/bash
|
||||
# Test 03: INF-02 Compliance Verification
|
||||
# Validates INF-02 requirement: private networks must NOT expose ports on 0.0.0.0
|
||||
# Usage: bash labs/lab-02-network/tests/03-inf02-compliance-test.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color definitions
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Get script directory
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$TEST_DIR/../.." && pwd)"
|
||||
|
||||
# Counter helpers
|
||||
pass_count=0
|
||||
fail_count=0
|
||||
skip_count=0
|
||||
|
||||
inc_pass() { ((pass_count++)) || true; }
|
||||
inc_fail() { ((fail_count++)) || true; }
|
||||
inc_skip() { ((skip_count++)) || true; }
|
||||
|
||||
# Test helper functions
|
||||
print_header() {
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
|
||||
}
|
||||
|
||||
print_test() {
|
||||
echo -e "\n${BLUE}[TEST]${NC} $1"
|
||||
}
|
||||
|
||||
print_pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $1"
|
||||
inc_pass
|
||||
}
|
||||
|
||||
print_fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $1"
|
||||
inc_fail
|
||||
}
|
||||
|
||||
print_skip() {
|
||||
echo -e "${YELLOW}[SKIP]${NC} $1"
|
||||
inc_skip
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
# INF-02 Requirement explanation
|
||||
print_header "Lab 02 - Test 03: INF-02 Compliance Verification"
|
||||
echo -e "${BLUE}INF-02 Requirement:${NC} Private networks must NOT expose ports on 0.0.0.0"
|
||||
echo -e "${YELLOW}Allowed:${NC} 127.0.0.1 (localhost only) or no published ports"
|
||||
echo -e "${RED}Forbidden:${NC} 0.0.0.0 (exposes to all network interfaces)"
|
||||
echo -e ""
|
||||
|
||||
# Compose file path
|
||||
COMPOSE_FILE="$PROJECT_ROOT/labs/lab-02-network/docker-compose.yml"
|
||||
|
||||
# Test 1: Verify docker-compose.yml exists
|
||||
print_test "Test 1: Verify docker-compose.yml exists"
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
print_pass "docker-compose.yml found at $COMPOSE_FILE"
|
||||
COMPOSE_EXISTS=true
|
||||
else
|
||||
print_skip "docker-compose.yml not found at $COMPOSE_FILE"
|
||||
print_info "This is expected in RED phase - file will be created in GREEN phase"
|
||||
COMPOSE_EXISTS=false
|
||||
|
||||
# Skip remaining tests if compose file doesn't exist
|
||||
print_header "Test Summary (Early Exit)"
|
||||
echo -e "Total tests: $((pass_count + fail_count + skip_count))"
|
||||
echo -e "${GREEN}Passed: $pass_count${NC}"
|
||||
echo -e "${YELLOW}Skipped: $skip_count${NC}"
|
||||
echo -e "${YELLOW}INF-02 compliance tests skipped - infrastructure not yet created${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Test 2: Verify docker-compose.yml is valid YAML
|
||||
print_test "Test 2: Validate docker-compose.yml syntax"
|
||||
if docker compose -f "$COMPOSE_FILE" config &> /dev/null; then
|
||||
print_pass "docker-compose.yml is valid YAML"
|
||||
else
|
||||
print_fail "docker-compose.yml has syntax errors"
|
||||
print_info "Run 'docker compose -f docker-compose.yml config' to see errors"
|
||||
fi
|
||||
|
||||
# Test 3: Check for 0.0.0.0 port bindings (CRITICAL - must not exist)
|
||||
print_test "Test 3: Check for 0.0.0.0 port bindings (VIOLATES INF-02)"
|
||||
print_info "Searching for pattern: 0.0.0.0:PORT"
|
||||
ZERO_DETECTIONS=$(grep -n -E '0\.0\.0\.0:[0-9]+' "$COMPOSE_FILE" 2>/dev/null || echo "")
|
||||
ZERO_COUNT=$(echo "$ZERO_DETECTIONS" | grep -c "0.0.0" || true)
|
||||
|
||||
if [[ -z "$ZERO_DETECTIONS" ]]; then
|
||||
print_pass "No 0.0.0.0 port bindings found (COMPLIANT with INF-02)"
|
||||
else
|
||||
print_fail "Found $ZERO_COUNT occurrence(s) of 0.0.0.0 port bindings - INF-02 VIOLATION!"
|
||||
echo "$ZERO_DETECTIONS" | while read -r line; do
|
||||
echo -e "${RED} $line${NC}"
|
||||
done
|
||||
print_warning "0.0.0.0 exposes service on ALL network interfaces (security risk)"
|
||||
print_info "Fix: Use '127.0.0.1:PORT:CONTAINER_PORT' for localhost-only access"
|
||||
fi
|
||||
|
||||
# Test 4: Check for host:port format without explicit host (defaults to 0.0.0.0)
|
||||
print_test "Test 4: Check for implicit 0.0.0.0 bindings (e.g., '8080:80' without host)"
|
||||
print_info "Pattern: '- \"PORT:CONTAINER_PORT\"' (defaults to 0.0.0.0:PORT)"
|
||||
|
||||
# Look for port mappings without explicit host (e.g., "8080:80" instead of "127.0.0.1:8080:80")
|
||||
IMPLICIT_ZERO=$(grep -n -E '^\s*-\s*"[0-9]+:[0-9]+' "$COMPOSE_FILE" 2>/dev/null || echo "")
|
||||
IMPLICIT_ZERO_ALT=$(grep -n -E '^\s*ports:\s*$' "$COMPOSE_FILE" -A 5 | grep -E '^\s+-\s*[0-9]+:[0-9]+' || echo "")
|
||||
|
||||
if [[ -z "$IMPLICIT_ZERO" && -z "$IMPLICIT_ZERO_ALT" ]]; then
|
||||
print_pass "No implicit 0.0.0.0 port bindings found"
|
||||
else
|
||||
if [[ -n "$IMPLICIT_ZERO" ]]; then
|
||||
print_fail "Found implicit 0.0.0.0 bindings (format: 'PORT:CONTAINER')"
|
||||
echo "$IMPLICIT_ZERO" | while read -r line; do
|
||||
echo -e "${RED} $line${NC}"
|
||||
done
|
||||
fi
|
||||
if [[ -n "$IMPLICIT_ZERO_ALT" ]]; then
|
||||
print_fail "Found implicit 0.0.0.0 bindings (ports: section)"
|
||||
echo "$IMPLICIT_ZERO_ALT" | while read -r line; do
|
||||
echo -e "${RED} $line${NC}"
|
||||
done
|
||||
fi
|
||||
print_warning "Port format 'PORT:CONTAINER' defaults to 0.0.0.0:PORT"
|
||||
print_info "Fix: Use '127.0.0.1:PORT:CONTAINER_PORT' for localhost-only binding"
|
||||
fi
|
||||
|
||||
# Test 5: Verify 127.0.0.1 bindings are used for private services
|
||||
print_test "Test 5: Verify private services use 127.0.0.1 binding (localhost only)"
|
||||
LOCALHOST_BINDINGS=$(grep -n -E '127\.0\.0\.1:[0-9]+' "$COMPOSE_FILE" 2>/dev/null || echo "")
|
||||
LOCALHOST_COUNT=$(echo "$LOCALHOST_BINDINGS" | grep -c "127.0.0" || true)
|
||||
|
||||
if [[ -n "$LOCALHOST_BINDINGS" ]]; then
|
||||
print_pass "Found $LOCALHOST_COUNT service(s) using 127.0.0.1 binding (secure)"
|
||||
echo "$LOCALHOST_BINDINGS" | while read -r line; do
|
||||
echo -e "${GREEN} $line${NC}"
|
||||
done
|
||||
else
|
||||
print_skip "No 127.0.0.1 bindings found - services may have no published ports (acceptable)"
|
||||
fi
|
||||
|
||||
# Test 6: Check for services with no published ports (most secure)
|
||||
print_test "Test 6: Check for services with no published ports (fully private)"
|
||||
print_info "Services with no 'ports:' section are fully internal (most secure)"
|
||||
|
||||
# Count services
|
||||
TOTAL_SERVICES=$(docker compose -f "$COMPOSE_FILE" config --services 2>/dev/null | wc -l)
|
||||
SERVICES_WITH_PORTS=$(grep -c -E '^\s+[a-z-]+:\s*$' "$COMPOSE_FILE" -A 20 | grep -c "ports:" || echo "0")
|
||||
|
||||
if [[ $TOTAL_SERVICES -gt 0 ]]; then
|
||||
PRIVATE_SERVICES=$((TOTAL_SERVICES - SERVICES_WITH_PORTS))
|
||||
print_pass "Services analysis: $TOTAL_SERVICES total, $SERVICES_WITH_PORTS with exposed ports, $PRIVATE_SERVICES fully private"
|
||||
print_info "Services with no published ports are accessible only within Docker networks"
|
||||
else
|
||||
print_skip "Could not count services"
|
||||
fi
|
||||
|
||||
# Test 7: Verify network configuration uses custom bridge networks
|
||||
print_test "Test 7: Verify custom bridge networks are defined (not default bridge)"
|
||||
NETWORKS_SECTION=$(grep -A 10 "^networks:" "$COMPOSE_FILE" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$NETWORKS_SECTION" ]]; then
|
||||
print_pass "Custom networks section found in docker-compose.yml"
|
||||
echo "$NETWORKS_SECTION" | head -5
|
||||
else
|
||||
print_skip "No custom networks defined (services use default bridge)"
|
||||
fi
|
||||
|
||||
# Test 8: Check for internal flag on private networks
|
||||
print_test "Test 8: Check for 'internal: true' on private networks"
|
||||
INTERNAL_NETWORKS=$(grep -B 5 -E 'internal:\s*true' "$COMPOSE_FILE" 2>/dev/null | grep -E '^\s+[a-z-]+:\s*$' || echo "")
|
||||
|
||||
if [[ -n "$INTERNAL_NETWORKS" ]]; then
|
||||
print_pass "Found internal networks (no external access)"
|
||||
echo "$INTERNAL_NETWORKS" | while read -r line; do
|
||||
echo -e "${GREEN} $line${NC}"
|
||||
done
|
||||
else
|
||||
print_skip "No internal networks found (acceptable - not all services need internal flag)"
|
||||
fi
|
||||
|
||||
# Test 9: Verify no host networking mode
|
||||
print_test "Test 9: Verify services don't use 'network_mode: host' (security risk)"
|
||||
HOST_NETWORK=$(grep -E 'network_mode:\s*host' "$COMPOSE_FILE" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$HOST_NETWORK" ]]; then
|
||||
print_pass "No services using host networking mode"
|
||||
else
|
||||
print_fail "Found 'network_mode: host' - VIOLATES isolation principles!"
|
||||
echo "$HOST_NETWORK" | while read -r line; do
|
||||
echo -e "${RED} $line${NC}"
|
||||
done
|
||||
print_warning "Host networking bypasses Docker network isolation"
|
||||
fi
|
||||
|
||||
# Test 10: Generate INF-02 compliance report
|
||||
print_test "Test 10: Generate INF-02 compliance summary"
|
||||
|
||||
# Collect all issues
|
||||
TOTAL_ISSUES=0
|
||||
if [[ $ZERO_COUNT -gt 0 ]]; then
|
||||
((TOTAL_ISSUES += ZERO_COUNT)) || true
|
||||
fi
|
||||
if [[ -n "$IMPLICIT_ZERO" || -n "$IMPLICIT_ZERO_ALT" ]]; then
|
||||
((TOTAL_ISSUES++)) || true
|
||||
fi
|
||||
if [[ -n "$HOST_NETWORK" ]]; then
|
||||
((TOTAL_ISSUES++)) || true
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}[*] INF-02 Compliance Report${NC}"
|
||||
echo "Compose file: $COMPOSE_FILE"
|
||||
echo "Total services: $TOTAL_SERVICES"
|
||||
echo "Services with exposed ports: $SERVICES_WITH_PORTS"
|
||||
echo "Fully private services: $PRIVATE_SERVICES"
|
||||
|
||||
if [[ $TOTAL_ISSUES -eq 0 ]]; then
|
||||
echo -e "\n${GREEN}[✓] INF-02 STATUS: COMPLIANT${NC}"
|
||||
print_pass "No security violations found"
|
||||
echo " - No 0.0.0.0 bindings"
|
||||
echo " - No implicit 0.0.0.0 bindings"
|
||||
echo " - No host networking mode"
|
||||
echo " - $PRIVATE_SERVICES services fully private"
|
||||
else
|
||||
echo -e "\n${RED}[✗] INF-02 STATUS: NON-COMPLIANT${NC}"
|
||||
print_fail "Found $TOTAL_ISSUES compliance issue(s)"
|
||||
echo " - 0.0.0.0 bindings: $ZERO_COUNT"
|
||||
echo " - Implicit bindings: $([[ -n "$IMPLICIT_ZERO" || -n "$IMPLICIT_ZERO_ALT" ]] && echo "yes" || echo "no")"
|
||||
echo " - Host networking: $([[ -n "$HOST_NETWORK" ]] && echo "yes" || echo "no")"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
print_header "Test Summary"
|
||||
echo -e "Total tests run: $((pass_count + fail_count + skip_count))"
|
||||
echo -e "${GREEN}Passed: $pass_count${NC}"
|
||||
if [[ $fail_count -gt 0 ]]; then
|
||||
echo -e "${RED}Failed: $fail_count${NC}"
|
||||
fi
|
||||
if [[ $skip_count -gt 0 ]]; then
|
||||
echo -e "${YELLOW}Skipped: $skip_count${NC}"
|
||||
fi
|
||||
|
||||
# Exit with appropriate code
|
||||
if [[ $fail_count -gt 0 ]]; then
|
||||
echo -e "\n${RED}INF-02 compliance tests FAILED${NC}"
|
||||
echo -e "Please fix the violations above before deploying to production."
|
||||
exit 1
|
||||
elif [[ $TOTAL_ISSUES -gt 0 ]]; then
|
||||
echo -e "\n${YELLOW}INF-02 compliance warnings detected${NC}"
|
||||
echo -e "Consider fixing the issues above for better security posture."
|
||||
exit 0
|
||||
else
|
||||
echo -e "\n${GREEN}All INF-02 compliance tests PASSED${NC}"
|
||||
echo -e "Infrastructure is compliant with security requirements."
|
||||
exit 0
|
||||
fi
|
||||
Reference in New Issue
Block a user