CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/jenkinsfile-toolkit

Complete jenkinsfile toolkit with generation and validation capabilities

97

Quality

97%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Overview
Quality
Evals
Security
Files

common_validation.shvalidator/scripts/

#!/bin/bash

# Common Validation Functions
# Shared validation functions for both Declarative and Scripted pipelines

# Note: We use set -u (undefined variable check) but not -e (exit on error)
# because grep returns exit code 1 when no match is found, which would cause
# premature script exit in conditional statements
set -uo pipefail

# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Counters
ERRORS=0
WARNINGS=0
INFO=0

# Validation result arrays
declare -a ERROR_MESSAGES=()
declare -a WARNING_MESSAGES=()
declare -a INFO_MESSAGES=()

log_error() {
    local line=$1
    local message=$2
    ERROR_MESSAGES+=("ERROR [Line $line]: $message")
    ((ERRORS++))
}

log_warning() {
    local line=$1
    local message=$2
    WARNING_MESSAGES+=("WARNING [Line $line]: $message")
    ((WARNINGS++))
}

log_info() {
    local line=$1
    local message=$2
    INFO_MESSAGES+=("INFO [Line $line]: $message")
    ((INFO++))
}

# Detect pipeline type
detect_pipeline_type() {
    local file=$1

    # Remove comments and empty lines
    local first_meaningful=$(grep -v '^\s*//' "$file" | grep -v '^\s*$' | head -1)

    if echo "$first_meaningful" | grep -q '^\s*pipeline\s*{'; then
        echo "declarative"
    elif echo "$first_meaningful" | grep -qE '^\s*node\s*(\(|{)'; then
        echo "scripted"
    elif grep -q '^\s*pipeline\s*{' "$file"; then
        echo "declarative"
    elif grep -qE '^\s*node\s*(\(|{)' "$file"; then
        echo "scripted"
    else
        echo "unknown"
    fi
}

# Check for hardcoded credentials
check_credentials() {
    local file=$1
    local line_num=0

    echo -e "${BLUE}=== Checking for Hardcoded Credentials ===${NC}"

    while IFS= read -r line; do
        ((++line_num))

        # Skip comments
        if echo "$line" | grep -qE '^\s*//'; then
            continue
        fi

        # Skip lines that are just comment markers in multi-line comments
        if echo "$line" | grep -qE '^\s*\*'; then
            continue
        fi

        # ============================================
        # PASSWORD DETECTION
        # ============================================

        # Check for password variable assignments: password = "value"
        if echo "$line" | grep -qiE '(password|passwd|pwd)\s*=\s*["\047]' && ! echo "$line" | grep -qi 'credentials'; then
            log_error "$line_num" "Potential hardcoded password detected"
            log_error "$line_num" "  → Use credentials() or withCredentials instead"
        fi

        # Check for command-line password flags: -p password, --password=secret
        # Look for -p followed by a non-variable value (not starting with $)
        # Exclude common false positives: mkdir -p, cp -p, tar -p, install -p, etc.
        if echo "$line" | grep -qiE '\s-p\s+[a-zA-Z0-9_]+' && ! echo "$line" | grep -qE '\s-p\s+\$'; then
            # Skip common commands that use -p for other purposes
            if ! echo "$line" | grep -qiE '(mkdir|cp|tar|install|rsync|scp|chmod)\s+-p'; then
                log_error "$line_num" "Password passed as command-line argument (-p)"
                log_error "$line_num" "  → Use withCredentials to inject secrets securely"
            fi
        fi

        # Check for --password flag with value
        if echo "$line" | grep -qiE -e '--password[=\s]+[a-zA-Z0-9_]+' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Password passed as command-line argument (--password)"
            log_error "$line_num" "  → Use withCredentials to inject secrets securely"
        fi

        # ============================================
        # API KEY / TOKEN DETECTION
        # ============================================

        # Check for API key variable assignments
        if echo "$line" | grep -qiE '(api[_-]?key|apikey)\s*=\s*["\047]' && ! echo "$line" | grep -qi 'credentials'; then
            log_error "$line_num" "Potential hardcoded API key detected"
            log_error "$line_num" "  → Use credentials() or withCredentials instead"
        fi

        # Check for token/secret variable assignments with long values
        if echo "$line" | grep -qiE '(token|secret)\s*=\s*["\047][a-zA-Z0-9]{20,}'; then
            log_warning "$line_num" "Potential hardcoded token/secret detected"
            log_warning "$line_num" "  → Use Jenkins Credentials Manager"
        fi

        # ============================================
        # BEARER TOKEN / AUTH HEADER DETECTION
        # ============================================

        # Check for Bearer tokens in Authorization headers
        if echo "$line" | grep -qiE 'Bearer\s+[a-zA-Z0-9_.-]{10,}' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Hardcoded Bearer token detected"
            log_error "$line_num" "  → Use withCredentials for token management"
        fi

        # Check for Basic auth with inline credentials (base64 or plain)
        if echo "$line" | grep -qiE 'Basic\s+[a-zA-Z0-9+/=]{10,}' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Hardcoded Basic auth credentials detected"
            log_error "$line_num" "  → Use withCredentials([usernamePassword(...)]) instead"
        fi

        # Check for X-API-Key, X-Auth-Token, and similar headers with inline values
        if echo "$line" | grep -qiE '(X-API-Key|X-Auth-Token|X-Access-Token|Authorization):\s*[a-zA-Z0-9_.-]{8,}' && ! echo "$line" | grep -qE '\$'; then
            log_warning "$line_num" "Hardcoded auth header value detected"
            log_warning "$line_num" "  → Use withCredentials for secure header injection"
        fi

        # ============================================
        # CLOUD PROVIDER CREDENTIALS
        # ============================================

        # Check for AWS Access Key IDs
        if echo "$line" | grep -qE 'AKIA[0-9A-Z]{16}'; then
            log_error "$line_num" "AWS Access Key ID detected - NEVER hardcode AWS credentials!"
            log_error "$line_num" "  → Use withCredentials or AWS credentials plugin"
        fi

        # Check for AWS Secret Access Keys (40 char base64-like string after aws_secret)
        if echo "$line" | grep -qiE 'aws_secret[_-]?(access[_-]?key)?\s*=\s*["\047][A-Za-z0-9/+=]{40}'; then
            log_error "$line_num" "AWS Secret Access Key detected"
            log_error "$line_num" "  → Use withCredentials or AWS credentials plugin"
        fi

        # Check for Azure credentials patterns
        if echo "$line" | grep -qiE '(azure[_-]?client[_-]?secret|azure[_-]?tenant)\s*=\s*["\047][a-zA-Z0-9-]{20,}'; then
            log_error "$line_num" "Azure credential detected"
            log_error "$line_num" "  → Use Azure Credentials plugin with withCredentials"
        fi

        # Check for GCP service account key indicators
        if echo "$line" | grep -qE '"type"\s*:\s*"service_account"'; then
            log_error "$line_num" "GCP service account key detected"
            log_error "$line_num" "  → Use GCP Credentials plugin or secret file credentials"
        fi

        # ============================================
        # VERSION CONTROL TOKENS
        # ============================================

        # Check for GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
        if echo "$line" | grep -qE 'gh[pousr]_[a-zA-Z0-9]{36,}'; then
            log_error "$line_num" "GitHub token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for GitLab tokens (glpat-)
        if echo "$line" | grep -qE 'glpat-[a-zA-Z0-9_-]{20,}'; then
            log_error "$line_num" "GitLab personal access token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for Bitbucket app passwords
        if echo "$line" | grep -qiE 'bitbucket.*["\047][a-zA-Z0-9]{20,}["\047]'; then
            log_warning "$line_num" "Potential Bitbucket credential detected"
            log_warning "$line_num" "  → Use credentials() with username/password"
        fi

        # ============================================
        # DOCKER / REGISTRY CREDENTIALS
        # ============================================

        # Check for docker login with inline password
        if echo "$line" | grep -qiE 'docker\s+login.*-p\s+[^\$]' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Docker login with hardcoded password"
            log_error "$line_num" "  → Use withCredentials([usernamePassword(credentialsId: 'docker-creds', ...)])"
        fi

        # Check for docker login with --password
        if echo "$line" | grep -qiE 'docker\s+login.*--password[=\s]+[a-zA-Z0-9]' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Docker login with hardcoded password"
            log_error "$line_num" "  → Use docker.withRegistry() with credentials"
        fi

        # ============================================
        # SSH / PRIVATE KEY DETECTION
        # ============================================

        # Check for private key content (use -e to avoid -- being interpreted as option)
        if echo "$line" | grep -qE -e 'BEGIN.*(RSA\s+)?PRIVATE\s+KEY'; then
            log_error "$line_num" "Private key content detected in pipeline"
            log_error "$line_num" "  → Use SSH credentials in Jenkins Credentials Manager"
        fi

        # Check for SSH key file paths with hardcoded values
        if echo "$line" | grep -qiE 'ssh.*-i\s+["\047]?(/[^\$]+|~[^\$]+)["\047]?' && ! echo "$line" | grep -qE '\$'; then
            log_warning "$line_num" "Hardcoded SSH key path detected"
            log_warning "$line_num" "  → Use sshUserPrivateKey credentials"
        fi

        # ============================================
        # DATABASE CREDENTIALS
        # ============================================

        # Check for database connection strings with credentials
        if echo "$line" | grep -qiE '(mysql|postgresql|postgres|mongodb|redis|jdbc)://[a-zA-Z0-9_]+:[a-zA-Z0-9_]+@'; then
            log_error "$line_num" "Database connection string with embedded credentials"
            log_error "$line_num" "  → Use withCredentials to inject database credentials"
        fi

        # Check for database password parameters
        if echo "$line" | grep -qiE '(db[_-]?pass|database[_-]?password|mysql[_-]?pwd)\s*=\s*["\047][a-zA-Z0-9]'; then
            log_error "$line_num" "Hardcoded database password"
            log_error "$line_num" "  → Use credentials() or withCredentials"
        fi

        # ============================================
        # GENERIC PATTERNS
        # ============================================

        # Check for common credential variable names with suspicious values
        if echo "$line" | grep -qiE '(username|user)\s*=\s*["\047](admin|root|sa|administrator)'; then
            log_warning "$line_num" "Hardcoded username detected - consider using credentials"
        fi

        # Check for slack/webhook tokens
        if echo "$line" | grep -qE 'xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}'; then
            log_error "$line_num" "Slack token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for generic webhook URLs with tokens
        if echo "$line" | grep -qiE 'hooks\.(slack|discord)\.com/services/[A-Z0-9/]+'; then
            log_warning "$line_num" "Webhook URL with token detected"
            log_warning "$line_num" "  → Store webhook URL in credentials"
        fi

        # Check for npm tokens
        if echo "$line" | grep -qE 'npm_[a-zA-Z0-9]{36}'; then
            log_error "$line_num" "NPM token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for PyPI tokens
        if echo "$line" | grep -qE 'pypi-[a-zA-Z0-9_-]{50,}'; then
            log_error "$line_num" "PyPI token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for base64 encoded credentials (common mistake) - long base64 strings
        if echo "$line" | grep -qE '["\047][A-Za-z0-9+/]{40,}={0,2}["\047]'; then
            log_info "$line_num" "Potential base64 encoded value - ensure this is not a credential"
        fi

        # Check for hex-encoded secrets (32+ hex chars often indicate secrets)
        if echo "$line" | grep -qE '["\047][a-fA-F0-9]{32,}["\047]' && ! echo "$line" | grep -qiE '(commit|sha|hash|checksum|md5|sha1|sha256)'; then
            log_info "$line_num" "Long hex string detected - verify this is not a secret"
        fi

    done < "$file"

    echo ""
}

# Check variable usage
check_variable_usage() {
    local file=$1
    local line_num=0

    echo -e "${BLUE}=== Checking Variable Usage ===${NC}"

    declare -A defined_vars
    declare -A used_vars

    while IFS= read -r line; do
        ((++line_num))

        # Skip comments
        if echo "$line" | grep -qE '^\s*//'; then
            continue
        fi

        # Track variable definitions
        if echo "$line" | grep -qE '(def|String|Integer|Boolean)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*='; then
            local var_name=$(echo "$line" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*\s*=' | sed 's/\s*=//')
            defined_vars["$var_name"]=$line_num
        fi

        # Track variable usage (simple heuristic)
        if echo "$line" | grep -qE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?'; then
            local vars=$(echo "$line" | grep -oE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?' | sed 's/[\${}]//g')
            for var in $vars; do
                used_vars["$var"]=$line_num
            done
        fi

        # Check for undefined variables being used
        if echo "$line" | grep -qE '\$[a-zA-Z_][a-zA-Z0-9_]*' && ! echo "$line" | grep -qE '(def|String|params\.|env\.)'; then
            local var=$(echo "$line" | grep -oE '\$[a-zA-Z_][a-zA-Z0-9_]*' | sed 's/\$//' | head -1)
            if [[ ! -v defined_vars["$var"] ]] && [[ "$var" != "WORKSPACE" ]] && [[ "$var" != "BUILD_NUMBER" ]] && [[ "$var" != "JOB_NAME" ]]; then
                log_info "$line_num" "Variable '\$$var' used but not explicitly defined - ensure it's set by environment"
            fi
        fi
    done < "$file"

    echo ""
}

# Detect plugin usage
detect_plugins() {
    local file=$1

    echo -e "${BLUE}=== Detecting Plugin Usage ===${NC}"

    declare -A plugins

    # Common plugin steps
    local plugin_patterns=(
        "docker\."
        "kubernetes\."
        "withCredentials"
        "git "
        "checkout"
        "junit"
        "archiveArtifacts"
        "publishHTML"
        "mail"
        "emailext"
        "slackSend"
        "build job:"
        "input "
        "timeout"
        "retry"
        "script"
    )

    for pattern in "${plugin_patterns[@]}"; do
        if grep -q "$pattern" "$file"; then
            local plugin_name=$(echo "$pattern" | sed 's/[\.\\].*$//' | sed 's/ $//')
            plugins["$plugin_name"]=1
        fi
    done

    if [ ${#plugins[@]} -gt 0 ]; then
        echo "Plugins detected:"
        for plugin in "${!plugins[@]}"; do
            echo "  - $plugin"
        done
    else
        echo "No common plugins detected"
    fi

    echo ""
}

# Check for multiple consecutive sh/bat steps
check_multiple_sh_steps() {
    local file=$1
    local line_num=0
    local consecutive_sh=0
    local first_sh_line=0

    echo -e "${BLUE}=== Checking for Multiple Shell Steps ===${NC}"

    while IFS= read -r line; do
        ((++line_num))

        # Check for sh/bat steps
        if echo "$line" | grep -qE '^\s*(sh|bat)\s+["\047]' || echo "$line" | grep -qE '^\s*(sh|bat)\s*\('; then
            if [ $consecutive_sh -eq 0 ]; then
                first_sh_line=$line_num
            fi
            ((consecutive_sh++))
        else
            # Not a sh/bat line
            if [ $consecutive_sh -gt 2 ]; then
                log_warning "$first_sh_line" "Found $consecutive_sh consecutive sh/bat steps"
                log_warning "$first_sh_line" "  → Combine into single sh step with triple-quoted string (sh '''...''')"
                log_warning "$first_sh_line" "  → See: references/best_practices.md#combine-shell-commands"
            fi
            consecutive_sh=0
        fi
    done < "$file"

    # Check at end of file
    if [ $consecutive_sh -gt 2 ]; then
        log_warning "$first_sh_line" "Found $consecutive_sh consecutive sh/bat steps"
        log_warning "$first_sh_line" "  → Combine into single sh step with triple-quoted string"
    fi

    echo ""
}

# Check for timeout usage
check_timeout_usage() {
    local file=$1

    echo -e "${BLUE}=== Checking Timeout Configuration ===${NC}"

    if ! grep -qE '(timeout|Timeout)' "$file"; then
        log_info 1 "No timeout configuration found - consider adding timeout to prevent hung builds"
        log_info 1 "  → Declarative: options { timeout(time: 1, unit: 'HOURS') }"
        log_info 1 "  → Scripted: timeout(time: 1, unit: 'HOURS') { ... }"
    fi

    echo ""
}

# Check for proper workspace cleanup
check_workspace_cleanup() {
    local file=$1

    echo -e "${BLUE}=== Checking Workspace Cleanup ===${NC}"

    if ! grep -qE '(cleanWs|deleteDir|ws)' "$file"; then
        log_info 1 "No workspace cleanup detected - consider cleaning workspace for reproducible builds"
        log_info 1 "  → Add cleanWs() in post section or use deleteDir()"
    fi

    echo ""
}

# Print validation results
print_results() {
    echo -e "${BLUE}=== Validation Results ===${NC}"
    echo ""

    if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
        echo -e "${RED}ERRORS (${ERRORS}):${NC}"
        for msg in "${ERROR_MESSAGES[@]}"; do
            echo -e "${RED}$msg${NC}"
        done
        echo ""
    fi

    if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
        echo -e "${YELLOW}WARNINGS (${WARNINGS}):${NC}"
        for msg in "${WARNING_MESSAGES[@]}"; do
            echo -e "${YELLOW}$msg${NC}"
        done
        echo ""
    fi

    if [ ${#INFO_MESSAGES[@]} -gt 0 ]; then
        echo -e "${BLUE}INFO (${INFO}):${NC}"
        for msg in "${INFO_MESSAGES[@]}"; do
            echo -e "${BLUE}$msg${NC}"
        done
        echo ""
    fi

    # Summary
    echo -e "${BLUE}=== Summary ===${NC}"
    if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
        echo -e "${GREEN}✓ Validation passed with no errors or warnings${NC}"
        return 0
    elif [ $ERRORS -eq 0 ]; then
        echo -e "${YELLOW}✓ Validation passed with $WARNINGS warning(s)${NC}"
        return 0
    else
        echo -e "${RED}✗ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)${NC}"
        return 1
    fi
}

# Main function - run all common checks
run_common_checks() {
    local file=$1

    echo -e "${BLUE}=== Running Common Validation Checks ===${NC}"
    echo "File: $file"
    echo ""

    check_credentials "$file" || true
    check_variable_usage "$file" || true
    detect_plugins "$file" || true
    check_multiple_sh_steps "$file" || true
    check_timeout_usage "$file" || true
    check_workspace_cleanup "$file" || true

    print_results
}

# If script is executed directly
if [ "${BASH_SOURCE[0]}" == "${0}" ]; then
    if [ $# -lt 2 ]; then
        echo "Usage: $0 <command> <jenkinsfile>"
        echo "Commands:"
        echo "  detect_type       - Detect pipeline type (declarative/scripted)"
        echo "  check_credentials - Check for hardcoded credentials"
        echo "  check_all        - Run all common validation checks"
        exit 1
    fi

    COMMAND=$1
    JENKINSFILE=$2

    if [ ! -f "$JENKINSFILE" ]; then
        echo -e "${RED}Error: File '$JENKINSFILE' not found${NC}"
        exit 1
    fi

    case "$COMMAND" in
        detect_type)
            TYPE=$(detect_pipeline_type "$JENKINSFILE")
            echo "Pipeline type: $TYPE"
            ;;
        check_credentials)
            check_credentials "$JENKINSFILE"
            print_results
            ;;
        check_all)
            run_common_checks "$JENKINSFILE"
            ;;
        *)
            echo "Unknown command: $COMMAND"
            exit 1
            ;;
    esac
fi

tile.json