--- phase: 02-lab-01-iam-sicurezza plan: 01 type: execute wave: 0 depends_on: [] files_modified: - labs/lab-01-iam/tests/test-01-user-creation.sh - labs/lab-01-iam/tests/test-02-docker-access.sh - labs/lab-01-iam/tests/03-non-root-test.sh - labs/lab-01-iam/tests/99-final-verification.sh - labs/lab-01-iam/tests/run-all-tests.sh autonomous: true requirements: [TEST-01, TEST-05, INF-01] user_setup: [] must_haves: truths: - "Test scripts exist and can validate user creation and Docker access" - "Test scripts verify non-root container execution (INF-01)" - "Final verification script runs all checks for student self-validation" - "Test harness can be executed with single command" artifacts: - path: "labs/lab-01-iam/tests/test-01-user-creation.sh" provides: "User and group creation validation" min_lines: 40 - path: "labs/lab-01-iam/tests/test-02-docker-access.sh" provides: "Docker socket access control validation" min_lines: 30 - path: "labs/lab-01-iam/tests/03-non-root-test.sh" provides: "Non-root container verification (INF-01)" min_lines: 35 - path: "labs/lab-01-iam/tests/99-final-verification.sh" provides: "Final double-check command for students" min_lines: 25 - path: "labs/lab-01-iam/tests/run-all-tests.sh" provides: "Test suite orchestration" min_lines: 15 key_links: - from: "run-all-tests.sh" to: "test-01-user-creation.sh, test-02-docker-access.sh, 03-non-root-test.sh, 99-final-verification.sh" via: "Sequential execution with exit code handling" pattern: "bash.*tests/.*\\.sh" --- Create test infrastructure following TDD methodology (RED phase first). Test scripts validate user creation, Docker socket access control, and non-root container execution before any implementation exists. Purpose: Enable Test-Driven Infrastructure (TDI) by writing failing tests first, then implementing infrastructure to make them pass. Output: Four test scripts (user creation, Docker access, non-root verification, final check) plus test orchestration script. @/home/luca/.claude/get-shit-done/workflows/execute-plan.md @/home/luca/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-lab-01-iam-sicurezza/02-RESEARCH.md @.planning/phases/02-lab-01-iam-sicurezza/02-VALIDATION.md @CLAUDE.md # Key patterns from RESEARCH.md ## TDD Methodology for Infrastructure ```bash # RED Phase: Test should fail initially (no infrastructure exists) # GREEN Phase: Implement minimum infrastructure to pass tests # REFACTOR Phase: Optimize without breaking tests # Example test structure from RESEARCH.md: test_unauthorized_access() { sudo useradd -m -s /bin/bash test_user 2>/dev/null || true if sudo -u test_user docker ps &>/dev/null; then echo "FAIL: test_user can access docker without being in docker group" return 1 else echo "PASS: test_user correctly denied access" return 0 fi } ``` ## INF-01 Verification Pattern ```bash # From RESEARCH.md - Non-root container verification for service in $(docker-compose ps --services); do container_name=$(docker-compose ps -q $service) actual_user=$(docker exec $container_name whoami 2>/dev/null) if [ "$actual_user" = "root" ]; then echo "FAIL: $service running as root" exit 1 fi done echo "PASS: All containers running as non-root" ``` ## Common Pitfalls to Handle - Group membership requires re-login (use `groups` command for testing) - Test as non-privileged user (root bypasses Docker socket permissions) - Verify with multiple methods: `docker exec whoami`, `docker inspect`, `docker top` ## Test Framework from RESEARCH.md - Framework: BASH (Bourne Again Shell) >= 4.0 - No config file needed - inline test functions - Quick run: `bash labs/lab-01-iam/tests/quick-test.sh` - Full suite: `bash labs/lab-01-iam/tests/run-all-tests.sh` Task 1: Create user creation test script labs/lab-01-iam/tests/test-01-user-creation.sh - Test 1: Non-existent user returns appropriate failure - Test 2: User not in docker group cannot access Docker socket - Test 3: User can be added to docker group - Test 4: Group membership verified with `groups` command Create test script for Linux user and group management: ```bash #!/bin/bash # Test: Linux user creation and Docker group membership # Phase: RED - This test will fail initially (no users configured) set -euo pipefail # Color output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color pass_count=0 fail_count=0 test_user_not_exists() { local user="lab01_student" if id "$user" &>/dev/null; then echo -e "${YELLOW}SKIP${NC}: User $user already exists" return 0 fi echo -e "${GREEN}PASS${NC}: User $user does not exist (test environment clean)" ((pass_count++)) return 0 } test_user_without_docker_group() { local user="lab01_student" # Create test user if doesn't exist if ! id "$user" &>/dev/null; then sudo useradd -m -s /bin/bash "$user" 2>/dev/null || true fi # Check if user is in docker group if groups "$user" 2>/dev/null | grep -q docker; then echo -e "${RED}FAIL${NC}: User $user is in docker group (should not be yet)" ((fail_count++)) return 1 fi echo -e "${GREEN}PASS${NC}: User $user is not in docker group" ((pass_count++)) return 0 } test_docker_access_denied() { local user="lab01_student" # Test that user cannot access docker socket if sudo -u "$user" docker ps &>/dev/null; then echo -e "${RED}FAIL${NC}: User $user can access docker without docker group membership" ((fail_count++)) return 1 fi echo -e "${GREEN}PASS${NC}: Docker access correctly denied for $user" ((pass_count++)) return 0 } # Run all tests echo "Running user creation tests..." echo "================================" test_user_not_exists test_user_without_docker_group test_docker_access_denied echo "================================" echo "Tests passed: $pass_count" echo "Tests failed: $fail_count" if [ $fail_count -gt 0 ]; then exit 1 fi exit 0 ``` Key implementation points: - Use `groups` command to verify group membership (handles re-login issue) - Run Docker commands as test user with `sudo -u` - Test the negative case first (user without access) - Return proper exit codes (0=pass, 1=fail) chmod +x labs/lab-01-iam/tests/test-01-user-creation.sh && bash labs/lab-01-iam/tests/test-01-user-creation.sh Script exists, is executable, and tests user/group creation behavior Task 2: Create Docker access control test script labs/lab-01-iam/tests/test-02-docker-access.sh - Test 1: User in docker group can execute docker ps - Test 2: User in docker group can run basic containers - Test 3: Socket permissions are correctly set (660 or stricter) - Test 4: Group membership propagation is verified Create test script for Docker socket access control: ```bash #!/bin/bash # Test: Docker socket access control via group membership # Phase: RED - This test will fail initially (no users configured) set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' pass_count=0 fail_count=0 test_socket_permissions() { local socket="/var/run/docker.sock" local perms=$(stat -c "%a" "$socket" 2>/dev/null || echo "000") # Socket should be 660 or stricter (no world-readable/writable) if [ "$perms" = "660" ] || [ "$perms" = "600" ]; then echo -e "${GREEN}PASS${NC}: Docker socket permissions are $perms" ((pass_count++)) return 0 else echo -e "${YELLOW}WARN${NC}: Docker socket permissions are $perms (expected 660)" ((pass_count++)) return 0 fi } test_docker_group_exists() { if getent group docker >/dev/null 2>&1; then echo -e "${GREEN}PASS${NC}: Docker group exists" ((pass_count++)) return 0 else echo -e "${RED}FAIL${NC}: Docker group does not exist" ((fail_count++)) return 1 fi } test_user_can_add_to_docker_group() { local user="lab01_student" # This test verifies the MECHANISM, not that it's done yet if command -v usermod >/dev/null 2>&1; then echo -e "${GREEN}PASS${NC}: usermod command available for group management" ((pass_count++)) return 0 else echo -e "${RED}FAIL${NC}: usermod command not available" ((fail_count++)) return 1 fi } test_docker_accessible_by_group() { # Check that docker group members can access the socket local socket_group=$(stat -c "%G" /var/run/docker.sock 2>/dev/null || echo "unknown") if [ "$socket_group" = "docker" ]; then echo -e "${GREEN}PASS${NC}: Docker socket owned by docker group" ((pass_count++)) return 0 else echo -e "${YELLOW}WARN${NC}: Docker socket owned by $socket_group (expected docker)" ((pass_count++)) return 0 fi } # Run all tests echo "Running Docker access control tests..." echo "======================================" test_socket_permissions test_docker_group_exists test_user_can_add_to_docker_group test_docker_accessible_by_group echo "======================================" echo "Tests passed: $pass_count" echo "Tests failed: $fail_count" if [ $fail_count -gt 0 ]; then exit 1 fi exit 0 ``` Key implementation points: - Verify socket ownership and permissions - Check docker group exists - Validate group management commands available - Test mechanism for adding users to docker group chmod +x labs/lab-01-iam/tests/test-02-docker-access.sh && bash labs/lab-01-iam/tests/test-02-docker-access.sh Script validates Docker socket access control mechanisms Task 3: Create non-root container verification script (INF-01) labs/lab-01-iam/tests/03-non-root-test.sh - Test 1: Container configured with USER directive runs as non-root - Test 2: docker exec whoami returns non-root user - Test 3: docker inspect shows User field set - Test 4: docker top shows non-root UID (not 0) Create test script for non-root container verification (INF-01 requirement): ```bash #!/bin/bash # Test: Non-root container execution (INF-01 requirement) # Phase: RED - This test will fail initially (no containers exist) set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' pass_count=0 fail_count=0 # Container name to test CONTAINER_NAME="lab01-test-container" test_non_root_dockerfile_exists() { local dockerfile="labs/lab-01-iam/Dockerfile.test" if [ -f "$dockerfile" ]; then echo -e "${GREEN}PASS${NC}: Test Dockerfile exists" ((pass_count++)) return 0 else echo -e "${YELLOW}SKIP${NC}: Test Dockerfile not created yet (expected in RED phase)" ((pass_count++)) return 0 fi } test_container_runs_as_non_root() { # Check if container exists if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo -e "${YELLOW}SKIP${NC}: Container ${CONTAINER_NAME} does not exist yet (expected in RED phase)" ((pass_count++)) return 0 fi # Check if container is running if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo -e "${YELLOW}SKIP${NC}: Container ${CONTAINER_NAME} not running" ((pass_count++)) return 0 fi # Method 1: docker exec whoami local actual_user=$(docker exec "${CONTAINER_NAME}" whoami 2>/dev/null || echo "unknown") if [ "$actual_user" = "root" ]; then echo -e "${RED}FAIL${NC}: Container running as root (whoami)" ((fail_count++)) return 1 elif [ "$actual_user" = "unknown" ]; then echo -e "${YELLOW}SKIP${NC}: Cannot determine user (container not running or no exec)" ((pass_count++)) return 0 else echo -e "${GREEN}PASS${NC}: Container running as non-root user: $actual_user" ((pass_count++)) return 0 fi } test_container_user_configured() { if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo -e "${YELLOW}SKIP${NC}: Container not created yet" ((pass_count++)) return 0 fi # Method 2: docker inspect for User field local user_config=$(docker inspect --format='{{.Config.User}}' "${CONTAINER_NAME}" 2>/dev/null || echo "") if [ -z "$user_config" ]; then # Check compose file for user directive (may override Dockerfile) if [ -f "labs/lab-01-iam/docker-compose.yml" ]; then if grep -q "user:" labs/lab-01-iam/docker-compose.yml; then echo -e "${GREEN}PASS${NC}: User directive found in docker-compose.yml" ((pass_count++)) return 0 fi fi echo -e "${YELLOW}WARN${NC}: No User directive found in Dockerfile or compose" ((pass_count++)) return 0 else echo -e "${GREEN}PASS${NC}: User configured in container: $user_config" ((pass_count++)) return 0 fi } test_no_container_runs_as_root() { # INF-01 requirement: NO container should run as root local compose_file="labs/lab-01-iam/docker-compose.yml" if [ ! -f "$compose_file" ]; then echo -e "${YELLOW}SKIP${NC}: docker-compose.yml not created yet" ((pass_count++)) return 0 fi # Get all services from compose file local services=$(docker-compose -f "$compose_file" ps --services 2>/dev/null || echo "") if [ -z "$services" ]; then echo -e "${YELLOW}SKIP${NC}: No services defined yet" ((pass_count++)) return 0 fi local root_containers=0 while IFS= read -r service; do if [ -n "$service" ]; then local container_name=$(docker-compose -f "$compose_file" ps -q "$service" 2>/dev/null || echo "") if [ -n "$container_name" ]; then local user=$(docker exec "$container_name" whoami 2>/dev/null || echo "unknown") if [ "$user" = "root" ]; then echo -e "${RED}FAIL${NC}: Service $service running as root" ((root_containers++)) fi fi fi done <<< "$services" if [ $root_containers -gt 0 ]; then echo -e "${RED}FAIL${NC}: $root_containers container(s) running as root (INF-01 violation)" ((fail_count++)) return 1 else echo -e "${GREEN}PASS${NC}: No containers running as root (INF-01 satisfied)" ((pass_count++)) return 0 fi } # Run all tests echo "Running non-root container tests (INF-01)..." echo "============================================" test_non_root_dockerfile_exists test_container_runs_as_non_root test_container_user_configured test_no_container_runs_as_root echo "============================================" echo "Tests passed: $pass_count" echo "Tests failed: $fail_count" if [ $fail_count -gt 0 ]; then exit 1 fi exit 0 ``` Key implementation points: - INF-01 requirement: NO container runs as root - Multiple verification methods (whoami, inspect, compose check) - Handle not-yet-created infrastructure gracefully (skip with warning) - Check all services in docker-compose.yml for compliance chmod +x labs/lab-01-iam/tests/03-non-root-test.sh && bash labs/lab-01-iam/tests/03-non-root-test.sh Script verifies INF-01: no container runs as root Task 4: Create final verification script (double-check for students) labs/lab-01-iam/tests/99-final-verification.sh - Test 1: All previous tests can be run - Test 2: Student can verify their work end-to-end - Test 3: Clear PASS/FAIL report for all requirements - Test 4: Exit code indicates overall success/failure Create final verification script for student self-check: ```bash #!/bin/bash # Final Verification: Lab 01 - IAM & Sicurezza # This is the "double check" command students run to verify their work # Usage: bash tests/99-final-verification.sh set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Lab 01 - IAM & Sicurezza${NC}" echo -e "${BLUE}Final Verification (Double Check)${NC}" echo -e "${BLUE}========================================${NC}" echo "" # Track overall results all_passed=true # Test 1: User and group configuration echo -e "${BLUE}[1/5] Checking user and group configuration...${NC}" if id lab01_student &>/dev/null; then echo -e " ${GREEN}✓${NC} User lab01_student exists" if groups lab01_student 2>/dev/null | grep -q docker; then echo -e " ${GREEN}✓${NC} User lab01_student is in docker group" else echo -e " ${RED}✗${NC} User lab01_student is NOT in docker group" all_passed=false fi else echo -e " ${YELLOW}○${NC} User lab01_student does not exist (not created yet)" all_passed=false fi echo "" # Test 2: Docker access control echo -e "${BLUE}[2/5] Checking Docker access control...${NC}" if sudo -u lab01_student docker ps &>/dev/null; then echo -e " ${GREEN}✓${NC} lab01_student can access Docker socket" else echo -e " ${RED}✗${NC} lab01_student cannot access Docker socket" echo -e " ${YELLOW} Hint: User may need to re-login for group membership to take effect${NC}" all_passed=false fi echo "" # Test 3: Non-root container execution (INF-01) echo -e "${BLUE}[3/5] Checking non-root container execution (INF-01)...${NC}" compose_file="labs/lab-01-iam/docker-compose.yml" if [ ! -f "$compose_file" ]; then echo -e " ${YELLOW}○${NC} docker-compose.yml not found" all_passed=false else echo -e " ${GREEN}✓${NC} docker-compose.yml exists" # Check for user directive in services if grep -A 10 "services:" "$compose_file" | grep -q "user:"; then echo -e " ${GREEN}✓${NC} Services configured with non-root user directive" else echo -e " ${RED}✗${NC} No user directive found in docker-compose.yml" all_passed=false fi # If containers are running, verify they're not root if docker-compose -f "$compose_file" ps --services 2>/dev/null | grep -q .; then local root_count=0 while IFS= read -r service; do [ -z "$service" ] && continue local container=$(docker-compose -f "$compose_file" ps -q "$service" 2>/dev/null || echo "") if [ -n "$container" ]; then local user=$(docker exec "$container" whoami 2>/dev/null || echo "unknown") if [ "$user" = "root" ]; then echo -e " ${RED}✗${NC} Service $service running as ROOT (INF-01 violation)" ((root_count++)) fi fi done <<< "$(docker-compose -f "$compose_file" ps --services 2>/dev/null)" if [ $root_count -eq 0 ]; then echo -e " ${GREEN}✓${NC} All running containers are non-root" else all_passed=false fi else echo -e " ${YELLOW}○${NC} No containers running (start with docker-compose up)" fi fi echo "" # Test 4: Documentation completeness (Diátaxis) echo -e "${BLUE}[4/5] Checking documentation (Diátaxis framework)...${NC}" doc_count=0 for doc_type in "tutorial" "how-to-guides" "reference" "explanation"; do if [ -d "labs/lab-01-iam/$doc_type" ]; then local md_files=$(find "labs/lab-01-iam/$doc_type" -name "*.md" 2>/dev/null | wc -l) if [ "$md_files" -gt 0 ]; then echo -e " ${GREEN}✓${NC} $doc_type: $md_files document(s)" ((doc_count++)) else echo -e " ${YELLOW}○${NC} $doc_type: directory exists but empty" fi else echo -e " ${RED}✗${NC} $doc_type: missing" fi done if [ $doc_count -eq 4 ]; then echo -e " ${GREEN}✓${NC} All 4 Diátaxis document types present" else echo -e " ${YELLOW}○${NC} $doc_count/4 Diátaxis document types present" all_passed=false fi echo "" # Test 5: IAM parallels documentation echo -e "${BLUE}[5/5] Checking IAM parallels explanation...${NC}" explanation_file="labs/lab-01-iam/explanation/docker-iam-parallels.md" if [ -f "$explanation_file" ]; then if grep -qi "IAM.*Linux\|Linux.*IAM" "$explanation_file"; then echo -e " ${GREEN}✓${NC} IAM parallels documented" else echo -e " ${YELLOW}○${NC} Explanation exists but IAM parallels unclear" fi if grep -qi "differenza\|difference" "$explanation_file"; then echo -e " ${GREEN}✓${NC} Local vs cloud differences documented" else echo -e " ${YELLOW}○${NC} Local vs cloud differences not clearly documented" fi else echo -e " ${RED}✗${NC} IAM parallels explanation not found" all_passed=false fi echo "" # Final summary echo -e "${BLUE}========================================${NC}" if [ "$all_passed" = true ]; then echo -e "${GREEN}ALL CHECKS PASSED${NC}" echo -e "${GREEN}Lab 01 is complete!${NC}" echo -e "${BLUE}========================================${NC}" exit 0 else echo -e "${RED}SOME CHECKS FAILED${NC}" echo -e "${YELLOW}Review the output above and complete the missing items${NC}" echo -e "${BLUE}========================================${NC}" exit 1 fi ``` Key implementation points: - Student-friendly double-check command - Clear visual indicators (✓ pass, ✗ fail, ○ skip) - Tests all phase requirements: LAB-01, INF-01, DOCT-01/02/03/04, PARA-01/03 - Exit code 0 for all-pass, 1 for any failure - Helpful hints for common issues (re-login for group membership) chmod +x labs/lab-01-iam/tests/99-final-verification.sh && bash labs/lab-01-iam/tests/99-final-verification.sh Student can run single command to verify all lab requirements are met Task 5: Create test orchestration script labs/lab-01-iam/tests/run-all-tests.sh - Test 1: Script executes all test files in sequence - Test 2: Script stops on first failure (fail-fast) - Test 3: Script aggregates results and provides summary - Test 4: Script can be run from project root Create test orchestration script: ```bash #!/bin/bash # Test Suite Runner: Lab 01 - IAM & Sicurezza # Runs all tests in sequence and provides summary # Usage: bash labs/lab-01-iam/tests/run-all-tests.sh set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$TEST_DIR/../.." && pwd)" cd "$PROJECT_ROOT" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Lab 01 Test Suite${NC}" echo -e "${BLUE}========================================${NC}" echo "" # Array of test files in order declare -a tests=( "$TEST_DIR/test-01-user-creation.sh" "$TEST_DIR/test-02-docker-access.sh" "$TEST_DIR/03-non-root-test.sh" ) total_tests=${#tests[@]} passed_tests=0 failed_tests=0 for i in "${!tests[@]}"; do test_num=$((i + 1)) test_file="${tests[$i]}" test_name=$(basename "$test_file") echo -e "${BLUE}[$test_num/$total_tests] Running $test_name...${NC}" if bash "$test_file"; then echo -e "${GREEN}✓ PASSED${NC}" echo "" ((passed_tests++)) else echo -e "${RED}✗ FAILED${NC}" echo "" ((failed_tests++)) # Fail-fast: stop on first failure break fi done # Summary echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Test Suite Summary${NC}" echo -e "${BLUE}========================================${NC}" echo "Passed: $passed_tests/$total_tests" echo "Failed: $failed_tests/$total_tests" echo "" if [ $failed_tests -eq 0 ]; then echo -e "${GREEN}All tests passed!${NC}" echo "" echo "Run final verification:" echo " bash labs/lab-01-iam/tests/99-final-verification.sh" echo -e "${BLUE}========================================${NC}" exit 0 else echo -e "${RED}Some tests failed${NC}" echo -e "${BLUE}========================================${NC}" exit 1 fi ``` Key implementation points: - Fail-fast approach (stops on first failure for TDD RED phase) - Executes tests in dependency order - Provides summary and next steps - Can be run from any directory (uses absolute paths) chmod +x labs/lab-01-iam/tests/run-all-tests.sh && bash labs/lab-01-iam/tests/run-all-tests.sh Orchestration script runs all tests and provides summary 1. All test scripts are executable (chmod +x) 2. Test scripts can run individually and return proper exit codes 3. Test orchestration script executes all tests in sequence 4. Test scripts follow TDD RED phase (will fail before implementation exists) 5. All tests handle missing infrastructure gracefully (skip with warning) 1. Test infrastructure is in place before any implementation (Wave 0 complete) 2. All requirement IDs (TEST-01, TEST-05, INF-01) have test coverage 3. Tests follow bash scripting best practices (set -euo pipefail, proper exit codes) 4. Student can run individual tests or full suite 5. Final verification script provides clear pass/fail report for all lab requirements After completion, create `.planning/phases/02-lab-01-iam-sicurezza/02-01-SUMMARY.md`