#!/bin/bash # Security Test Suite for mockupAWS v1.0.0 # Runs all security tests: dependency scanning, SAST, container scanning, secrets scanning set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPORTS_DIR="$SCRIPT_DIR/../reports" TIMESTAMP=$(date +%Y%m%d_%H%M%S) # Configuration SEVERITY_THRESHOLD="high" EXIT_ON_CRITICAL=true echo -e "${BLUE}========================================${NC}" echo -e "${BLUE} mockupAWS v1.0.0 Security Tests${NC}" echo -e "${BLUE}========================================${NC}" echo "" echo "Timestamp: $TIMESTAMP" echo "Reports Directory: $REPORTS_DIR" echo "" # Create reports directory mkdir -p "$REPORTS_DIR" # Initialize report REPORT_FILE="$REPORTS_DIR/${TIMESTAMP}_security_report.json" echo '{ "scan_date": "'$(date -Iseconds)'", "version": "1.0.0", "scans": {}, "summary": { "total_vulnerabilities": 0, "critical": 0, "high": 0, "medium": 0, "low": 0 }, "passed": true }' > "$REPORT_FILE" # ============================================ # 1. DEPENDENCY SCANNING (Snyk) # ============================================ run_snyk_scan() { echo -e "${YELLOW}Running Snyk dependency scan...${NC}" if ! command -v snyk &> /dev/null; then echo -e "${RED}Warning: Snyk CLI not installed. Skipping...${NC}" echo "Install from: https://docs.snyk.io/snyk-cli/install-the-snyk-cli" return 0 fi # Python dependencies if [ -f "pyproject.toml" ]; then echo "Scanning Python dependencies..." snyk test --file=pyproject.toml --json-file-output="$REPORTS_DIR/${TIMESTAMP}_snyk_python.json" || true fi # Node.js dependencies if [ -f "frontend/package.json" ]; then echo "Scanning Node.js dependencies..." (cd frontend && snyk test --json-file-output="../$REPORTS_DIR/${TIMESTAMP}_snyk_nodejs.json") || true fi # Generate summary SNYK_CRITICAL=0 SNYK_HIGH=0 SNYK_MEDIUM=0 SNYK_LOW=0 for file in "$REPORTS_DIR"/${TIMESTAMP}_snyk_*.json; do if [ -f "$file" ]; then CRITICAL=$(cat "$file" | jq '[.vulnerabilities[]?.severity == "critical"] | map(select(.)) | length' 2>/dev/null || echo 0) HIGH=$(cat "$file" | jq '[.vulnerabilities[]?.severity == "high"] | map(select(.)) | length' 2>/dev/null || echo 0) MEDIUM=$(cat "$file" | jq '[.vulnerabilities[]?.severity == "medium"] | map(select(.)) | length' 2>/dev/null || echo 0) LOW=$(cat "$file" | jq '[.vulnerabilities[]?.severity == "low"] | map(select(.)) | length' 2>/dev/null || echo 0) SNYK_CRITICAL=$((SNYK_CRITICAL + CRITICAL)) SNYK_HIGH=$((SNYK_HIGH + HIGH)) SNYK_MEDIUM=$((SNYK_MEDIUM + MEDIUM)) SNYK_LOW=$((SNYK_LOW + LOW)) fi done echo -e "${GREEN}✓ Snyk scan completed${NC}" echo " Critical: $SNYK_CRITICAL, High: $SNYK_HIGH, Medium: $SNYK_MEDIUM, Low: $SNYK_LOW" # Update report jq ".scans.snyk = { \"critical\": $SNYK_CRITICAL, \"high\": $SNYK_HIGH, \"medium\": $SNYK_MEDIUM, \"low\": $SNYK_LOW } | .summary.critical += $SNYK_CRITICAL | .summary.high += $SNYK_HIGH | .summary.medium += $SNYK_MEDIUM | .summary.low += $SNYK_LOW" \ "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" if [ "$SNYK_CRITICAL" -gt 0 ] && [ "$EXIT_ON_CRITICAL" = true ]; then echo -e "${RED}✗ Critical vulnerabilities found in dependencies!${NC}" return 1 fi } # ============================================ # 2. SAST SCANNING (SonarQube) # ============================================ run_sonar_scan() { echo -e "${YELLOW}Running SonarQube SAST scan...${NC}" if ! command -v sonar-scanner &> /dev/null; then echo -e "${RED}Warning: SonarScanner not installed. Skipping...${NC}" return 0 fi # Create sonar-project.properties if not exists if [ ! -f "sonar-project.properties" ]; then cat > sonar-project.properties << EOF sonar.projectKey=mockupaws sonar.projectName=mockupAWS sonar.projectVersion=1.0.0 sonar.sources=src,frontend/src sonar.exclusions=**/venv/**,**/node_modules/**,**/*.spec.ts,**/tests/** sonar.python.version=3.11 sonar.javascript.lcov.reportPaths=frontend/coverage/lcov.info sonar.python.coverage.reportPaths=coverage.xml EOF fi # Run scan sonar-scanner \ -Dsonar.login="${SONAR_TOKEN:-}" \ -Dsonar.host.url="${SONAR_HOST_URL:-http://localhost:9000}" \ 2>&1 | tee "$REPORTS_DIR/${TIMESTAMP}_sonar.log" || true echo -e "${GREEN}✓ SonarQube scan completed${NC}" # Extract issues from SonarQube API (requires token) if [ -n "$SONAR_TOKEN" ]; then SONAR_CRITICAL=$(curl -s -u "$SONAR_TOKEN:" "${SONAR_HOST_URL:-http://localhost:9000}/api/issues/search?componentKeys=mockupaws&severities=CRITICAL" | jq '.total' 2>/dev/null || echo 0) SONAR_HIGH=$(curl -s -u "$SONAR_TOKEN:" "${SONAR_HOST_URL:-http://localhost:9000}/api/issues/search?componentKeys=mockupaws&severities=BLOCKER,CRITICAL,MAJOR" | jq '.total' 2>/dev/null || echo 0) jq ".scans.sonarqube = { \"critical\": $SONAR_CRITICAL, \"high_issues\": $SONAR_HIGH } | .summary.critical += $SONAR_CRITICAL | .summary.high += $SONAR_HIGH" \ "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" fi } # ============================================ # 3. CONTAINER SCANNING (Trivy) # ============================================ run_trivy_scan() { echo -e "${YELLOW}Running Trivy container scan...${NC}" if ! command -v trivy &> /dev/null; then echo -e "${RED}Warning: Trivy not installed. Skipping...${NC}" echo "Install from: https://aquasecurity.github.io/trivy/latest/getting-started/installation/" return 0 fi # Scan filesystem trivy fs --exit-code 0 --format json --output "$REPORTS_DIR/${TIMESTAMP}_trivy_fs.json" . || true # Scan Dockerfile if exists if [ -f "Dockerfile" ]; then trivy config --exit-code 0 --format json --output "$REPORTS_DIR/${TIMESTAMP}_trivy_config.json" Dockerfile || true fi # Scan docker-compose if exists if [ -f "docker-compose.yml" ]; then trivy config --exit-code 0 --format json --output "$REPORTS_DIR/${TIMESTAMP}_trivy_compose.json" docker-compose.yml || true fi # Generate summary TRIVY_CRITICAL=0 TRIVY_HIGH=0 TRIVY_MEDIUM=0 TRIVY_LOW=0 for file in "$REPORTS_DIR"/${TIMESTAMP}_trivy_*.json; do if [ -f "$file" ]; then CRITICAL=$(cat "$file" | jq '[.Results[]?.Vulnerabilities[]?.Severity == "CRITICAL"] | map(select(.)) | length' 2>/dev/null || echo 0) HIGH=$(cat "$file" | jq '[.Results[]?.Vulnerabilities[]?.Severity == "HIGH"] | map(select(.)) | length' 2>/dev/null || echo 0) MEDIUM=$(cat "$file" | jq '[.Results[]?.Vulnerabilities[]?.Severity == "MEDIUM"] | map(select(.)) | length' 2>/dev/null || echo 0) LOW=$(cat "$file" | jq '[.Results[]?.Vulnerabilities[]?.Severity == "LOW"] | map(select(.)) | length' 2>/dev/null || echo 0) TRIVY_CRITICAL=$((TRIVY_CRITICAL + CRITICAL)) TRIVY_HIGH=$((TRIVY_HIGH + HIGH)) TRIVY_MEDIUM=$((TRIVY_MEDIUM + MEDIUM)) TRIVY_LOW=$((TRIVY_LOW + LOW)) fi done echo -e "${GREEN}✓ Trivy scan completed${NC}" echo " Critical: $TRIVY_CRITICAL, High: $TRIVY_HIGH, Medium: $TRIVY_MEDIUM, Low: $TRIVY_LOW" jq ".scans.trivy = { \"critical\": $TRIVY_CRITICAL, \"high\": $TRIVY_HIGH, \"medium\": $TRIVY_MEDIUM, \"low\": $TRIVY_LOW } | .summary.critical += $TRIVY_CRITICAL | .summary.high += $TRIVY_HIGH | .summary.medium += $TRIVY_MEDIUM | .summary.low += $TRIVY_LOW" \ "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" if [ "$TRIVY_CRITICAL" -gt 0 ] && [ "$EXIT_ON_CRITICAL" = true ]; then echo -e "${RED}✗ Critical vulnerabilities found in containers!${NC}" return 1 fi } # ============================================ # 4. SECRETS SCANNING (GitLeaks) # ============================================ run_gitleaks_scan() { echo -e "${YELLOW}Running GitLeaks secrets scan...${NC}" if ! command -v gitleaks &> /dev/null; then echo -e "${RED}Warning: GitLeaks not installed. Skipping...${NC}" echo "Install from: https://github.com/gitleaks/gitleaks" return 0 fi # Create .gitleaks.toml config if not exists if [ ! -f ".gitleaks.toml" ]; then cat > .gitleaks.toml << 'EOF' title = "mockupAWS GitLeaks Config" [extend] useDefault = true [[rules]] id = "mockupaws-api-key" description = "mockupAWS API Key" regex = '''mk_[a-zA-Z0-9]{32,}''' tags = ["apikey", "mockupaws"] [allowlist] paths = [ '''tests/''', '''e2e/''', '''\.venv/''', '''node_modules/''', ] EOF fi # Run scan gitleaks detect --source . --verbose --redact --report-format json --report-path "$REPORTS_DIR/${TIMESTAMP}_gitleaks.json" || true # Count findings if [ -f "$REPORTS_DIR/${TIMESTAMP}_gitleaks.json" ]; then GITLEAKS_FINDINGS=$(cat "$REPORTS_DIR/${TIMESTAMP}_gitleaks.json" | jq 'length' 2>/dev/null || echo 0) else GITLEAKS_FINDINGS=0 fi echo -e "${GREEN}✓ GitLeaks scan completed${NC}" echo " Secrets found: $GITLEAKS_FINDINGS" jq ".scans.gitleaks = { \"findings\": $GITLEAKS_FINDINGS } | .summary.high += $GITLEAKS_FINDINGS" \ "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" if [ "$GITLEAKS_FINDINGS" -gt 0 ]; then echo -e "${RED}✗ Potential secrets detected!${NC}" return 1 fi } # ============================================ # 5. OWASP ZAP SCAN # ============================================ run_zap_scan() { echo -e "${YELLOW}Running OWASP ZAP scan...${NC}" # Check if ZAP is available (via Docker) if ! command -v docker &> /dev/null; then echo -e "${RED}Warning: Docker not available for ZAP scan. Skipping...${NC}" return 0 fi TARGET_URL="${ZAP_TARGET_URL:-http://localhost:8000}" echo "Target URL: $TARGET_URL" # Run ZAP baseline scan docker run --rm -t \ -v "$REPORTS_DIR:/zap/wrk" \ ghcr.io/zaproxy/zaproxy:stable \ zap-baseline.py \ -t "$TARGET_URL" \ -J "${TIMESTAMP}_zap_report.json" \ -r "${TIMESTAMP}_zap_report.html" \ -w "${TIMESTAMP}_zap_report.md" \ -a || true # Count findings if [ -f "$REPORTS_DIR/${TIMESTAMP}_zap_report.json" ]; then ZAP_HIGH=$(cat "$REPORTS_DIR/${TIMESTAMP}_zap_report.json" | jq '[.site[0].alerts[] | select(.riskcode >= "3")] | length' 2>/dev/null || echo 0) ZAP_MEDIUM=$(cat "$REPORTS_DIR/${TIMESTAMP}_zap_report.json" | jq '[.site[0].alerts[] | select(.riskcode == "2")] | length' 2>/dev/null || echo 0) ZAP_LOW=$(cat "$REPORTS_DIR/${TIMESTAMP}_zap_report.json" | jq '[.site[0].alerts[] | select(.riskcode == "1")] | length' 2>/dev/null || echo 0) else ZAP_HIGH=0 ZAP_MEDIUM=0 ZAP_LOW=0 fi echo -e "${GREEN}✓ OWASP ZAP scan completed${NC}" echo " High: $ZAP_HIGH, Medium: $ZAP_MEDIUM, Low: $ZAP_LOW" jq ".scans.zap = { \"high\": $ZAP_HIGH, \"medium\": $ZAP_MEDIUM, \"low\": $ZAP_LOW } | .summary.high += $ZAP_HIGH | .summary.medium += $ZAP_MEDIUM | .summary.low += $ZAP_LOW" \ "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" } # ============================================ # 6. CUSTOM SECURITY CHECKS # ============================================ run_custom_checks() { echo -e "${YELLOW}Running custom security checks...${NC}" local issues=0 # Check for hardcoded secrets in source code echo "Checking for hardcoded secrets..." if grep -r -n "password.*=.*['\"][^'\"]\{8,\}['\"]" --include="*.py" --include="*.ts" --include="*.js" src/ frontend/src/ 2>/dev/null | grep -v "test\|example\|placeholder"; then echo -e "${RED}✗ Potential hardcoded passwords found${NC}" ((issues++)) fi # Check for TODO/FIXME security comments echo "Checking for security TODOs..." if grep -r -n "TODO.*security\|FIXME.*security\|XXX.*security" --include="*.py" --include="*.ts" --include="*.md" . 2>/dev/null; then echo -e "${YELLOW}! Security-related TODOs found${NC}" fi # Check JWT secret configuration echo "Checking JWT configuration..." if [ -f ".env" ]; then JWT_SECRET=$(grep "JWT_SECRET_KEY" .env | cut -d= -f2) if [ -n "$JWT_SECRET" ] && [ ${#JWT_SECRET} -lt 32 ]; then echo -e "${RED}✗ JWT_SECRET_KEY is too short (< 32 chars)${NC}" ((issues++)) fi fi # Check for debug mode in production if [ -f ".env" ]; then DEBUG=$(grep "DEBUG" .env | grep -i "true" || true) if [ -n "$DEBUG" ]; then echo -e "${YELLOW}! DEBUG mode is enabled${NC}" fi fi echo -e "${GREEN}✓ Custom security checks completed${NC}" jq ".scans.custom = { \"issues_found\": $issues } | .summary.high += $issues" "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" } # ============================================ # MAIN EXECUTION # ============================================ echo -e "${BLUE}Starting security scans...${NC}" echo "" # Run all scans run_snyk_scan || true run_sonar_scan || true run_trivy_scan || true run_gitleaks_scan || true run_zap_scan || true run_custom_checks || true # Generate summary echo "" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE} SECURITY SCAN SUMMARY${NC}" echo -e "${BLUE}========================================${NC}" echo "" # Calculate totals TOTAL_CRITICAL=$(jq '.summary.critical' "$REPORT_FILE") TOTAL_HIGH=$(jq '.summary.high' "$REPORT_FILE") TOTAL_MEDIUM=$(jq '.summary.medium' "$REPORT_FILE") TOTAL_LOW=$(jq '.summary.low' "$REPORT_FILE") TOTAL=$((TOTAL_CRITICAL + TOTAL_HIGH + TOTAL_MEDIUM + TOTAL_LOW)) echo "Total Vulnerabilities: $TOTAL" echo " Critical: $TOTAL_CRITICAL" echo " High: $TOTAL_HIGH" echo " Medium: $TOTAL_MEDIUM" echo " Low: $TOTAL_LOW" echo "" # Determine pass/fail if [ "$TOTAL_CRITICAL" -eq 0 ]; then echo -e "${GREEN}✓ SECURITY CHECK PASSED${NC}" echo " No critical vulnerabilities found." jq '.passed = true' "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" exit_code=0 else echo -e "${RED}✗ SECURITY CHECK FAILED${NC}" echo " Critical vulnerabilities must be resolved before deployment." jq '.passed = false' "$REPORT_FILE" > "$REPORTS_DIR/tmp.json" && mv "$REPORTS_DIR/tmp.json" "$REPORT_FILE" exit_code=1 fi echo "" echo -e "${BLUE}Report saved to: $REPORT_FILE${NC}" echo "" exit $exit_code