#!/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