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:
Luca Sacchi Ricciardi
2026-03-25 17:26:35 +01:00
parent d4c4f7d717
commit 5b2c8c37aa
22 changed files with 3988 additions and 12 deletions

View 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