#!/usr/bin/env bash
#########################################################################################
#### tetragon-policy-deployer.sh — Deploy and manage Cilium Tetragon eBPF tracing    ####
#### policies on Kubernetes. Apply, validate, audit, and export TracingPolicies       ####
#### Requires: bash 4+, kubectl, optionally helm                                     ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.00                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./tetragon-policy-deployer.sh --apply --policy ./policies/                    ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################
set -euo pipefail

VERSION="1.00"

# --- ANSI color variables (pre-initialized) ---
RED=""
GREEN=""
YELLOW=""
BLUE=""
CYAN=""
BOLD=""
DIM=""
RESET=""

# --- Defaults ---
MODE=""
POLICY_PATH=""
NAMESPACE="${TETRAGON_NAMESPACE:-kube-system}"
OUTPUT_DIR="."
VALUES_FILE=""
CONFIRM_YES=false
KUBECONFIG_FILE="${KUBECONFIG:-}"
KUBE_CTX="${KUBE_CONTEXT:-}"
OUTPUT_FORMAT="text"
VERBOSE_FLAG="${VERBOSE:-false}"
COLOR_FLAG="${COLOR:-true}"
GENERATE_TEMPLATE=""
DELETE_ALL=false
DELETE_NAME=""

# --- Color setup ---
setup_colors() {
    if [[ "$COLOR_FLAG" == "true" ]] && [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[1;33m'
        BLUE='\033[0;34m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    fi
}

# --- Logging ---
log()     { printf "%b\n" "${GREEN}✔${RESET} $*"; }
warn()    { printf "%b\n" "${YELLOW}⚠${RESET} $*" >&2; }
err()     { printf "%b\n" "${RED}✖${RESET} $*" >&2; }
verbose() { [[ "$VERBOSE_FLAG" == "true" ]] && printf "%b\n" "${DIM}▸ $*${RESET}" >&2; return 0; }
die()     { err "$*"; exit 1; }

section_header() {
    printf "\n%b━━━ %s ━━━%b\n" "${BOLD}${BLUE}" "$1" "${RESET}"
}

field() {
    printf "  %-22s %s\n" "$1:" "$2"
}

field_color() {
    printf "  %-22s %b%s%b\n" "$1:" "$2" "$3" "${RESET}"
}

# --- kubectl / helm wrappers ---
kubectl_cmd() {
    local -a args=("kubectl")
    [[ -n "$KUBECONFIG_FILE" ]] && args+=("--kubeconfig" "$KUBECONFIG_FILE")
    [[ -n "$KUBE_CTX" ]] && args+=("--context" "$KUBE_CTX")
    "${args[@]}" "$@"
}

helm_cmd() {
    local -a args=("helm")
    [[ -n "$KUBECONFIG_FILE" ]] && args+=("--kubeconfig" "$KUBECONFIG_FILE")
    [[ -n "$KUBE_CTX" ]] && args+=("--kube-context" "$KUBE_CTX")
    "${args[@]}" "$@"
}

# --- Dependency checks ---
require_kubectl() {
    command -v kubectl >/dev/null 2>&1 || die "kubectl is required but not found in PATH"
}

require_helm() {
    command -v helm >/dev/null 2>&1 || die "helm is required but not found in PATH"
}

# --- Usage ---
usage() {
    cat <<EOF
${BOLD}Tetragon Policy Deployer v${VERSION}${RESET}
Deploy, manage, and validate Cilium Tetragon eBPF tracing policies on Kubernetes.

${BOLD}MODES:${RESET}
  --install                Install Tetragon via Helm
  --apply                  Apply TracingPolicy manifests
  --audit                  Audit deployed tracing policies
  --status                 Quick Tetragon status check
  --export                 Export deployed policies to files
  --delete                 Remove tracing policies
  --generate TEMPLATE      Generate a TracingPolicy from a built-in template

${BOLD}OPTIONS:${RESET}
  --policy DIR|FILE        Path to TracingPolicy manifests
  --namespace NS           Tetragon namespace (default: kube-system)
  --output-dir DIR         Output directory for export
  --values FILE            Helm values file for install
  --yes                    Skip confirmation prompts
  --kubeconfig FILE        Path to kubeconfig
  --context CTX            Kubernetes context
  --format FORMAT          Output format: text (default), json, yaml
  --verbose                Enable verbose output
  --no-color               Disable colored output
  --help                   Show this help message

${BOLD}TEMPLATES (for --generate):${RESET}
  file-monitor             Monitor file access to sensitive paths
  process-exec             Track all process executions
  network-connect          Monitor outbound network connections
  privilege-escalation     Detect setuid/setgid/capabilities changes
  sensitive-mount          Monitor container volume mounts to host paths
  crypto-mining            Detect common crypto miner behaviors

${BOLD}ENVIRONMENT VARIABLES:${RESET}
  KUBECONFIG               Path to kubeconfig file
  KUBE_CONTEXT             Kubernetes context name
  TETRAGON_NAMESPACE       Tetragon namespace (default: kube-system)
  TETRAGON_POLICY_DIR      Default policy directory
  VERBOSE                  Enable verbose output (true/false)
  COLOR                    Enable colored output (true/false)

${BOLD}EXAMPLES:${RESET}
  ./tetragon-policy-deployer.sh --install
  ./tetragon-policy-deployer.sh --apply --policy ./policies/
  ./tetragon-policy-deployer.sh --audit
  ./tetragon-policy-deployer.sh --generate file-monitor > policy.yaml
  ./tetragon-policy-deployer.sh --delete --policy my-policy --yes
EOF
    exit 0
}

# --- Parse arguments ---
parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --install)          MODE="install"; shift ;;
            --apply)            MODE="apply"; shift ;;
            --audit)            MODE="audit"; shift ;;
            --status)           MODE="status"; shift ;;
            --export)           MODE="export"; shift ;;
            --delete)           MODE="delete"; shift ;;
            --generate)         MODE="generate"; GENERATE_TEMPLATE="${2:-}"; shift 2 || die "--generate requires a template name" ;;
            --policy)           POLICY_PATH="${2:-}"; shift 2 || die "--policy requires a path or name" ;;
            --namespace)        NAMESPACE="${2:-}"; shift 2 || die "--namespace requires a value" ;;
            --output-dir)       OUTPUT_DIR="${2:-}"; shift 2 || die "--output-dir requires a path" ;;
            --values)           VALUES_FILE="${2:-}"; shift 2 || die "--values requires a file path" ;;
            --yes)              CONFIRM_YES=true; shift ;;
            --all)              DELETE_ALL=true; shift ;;
            --kubeconfig)       KUBECONFIG_FILE="${2:-}"; shift 2 || die "--kubeconfig requires a file" ;;
            --context)          KUBE_CTX="${2:-}"; shift 2 || die "--context requires a name" ;;
            --format)           OUTPUT_FORMAT="${2:-}"; shift 2 || die "--format requires a value" ;;
            --verbose)          VERBOSE_FLAG="true"; shift ;;
            --no-color)         COLOR_FLAG="false"; shift ;;
            --help)             usage ;;
            *)                  die "Unknown option: $1" ;;
        esac
    done
    if [[ -z "$MODE" ]]; then err "No mode specified"; echo ""; usage; exit 1; fi
}

# --- Install Tetragon ---
do_install() {
    section_header "Installing Tetragon"
    require_kubectl
    require_helm

    log "Adding Cilium Helm repository"
    verbose "helm repo add cilium https://helm.cilium.io"
    helm_cmd repo add cilium https://helm.cilium.io 2>/dev/null || true
    helm_cmd repo update

    local -a install_args=("upgrade" "--install" "tetragon" "cilium/tetragon"
        "--namespace" "$NAMESPACE" "--create-namespace")
    [[ -n "$VALUES_FILE" ]] && install_args+=("--values" "$VALUES_FILE")

    log "Installing Tetragon chart into namespace ${CYAN}${NAMESPACE}${RESET}"
    verbose "helm ${install_args[*]}"
    helm_cmd "${install_args[@]}"

    log "Waiting for Tetragon DaemonSet to be ready"
    kubectl_cmd rollout status daemonset/tetragon -n "$NAMESPACE" --timeout=120s

    log "Verifying Tetragon pods"
    local pods
    pods=$(kubectl_cmd get pods -n "$NAMESPACE" -l app.kubernetes.io/name=tetragon \
        -o jsonpath='{range .items[*]}{.metadata.name} {.status.phase}{"\n"}{end}')

    if [[ -z "$pods" ]]; then
        warn "No Tetragon pods found"
    else
        local running=0 total=0
        while IFS= read -r line; do
            [[ -z "$line" ]] && continue
            total=$((total + 1))
            local phase
            phase=$(echo "$line" | awk '{print $2}')
            if [[ "$phase" == "Running" ]]; then
                running=$((running + 1))
            fi
        done <<< "$pods"
        field_color "Pods" "${GREEN}" "${running}/${total} running"
    fi

    log "Tetragon installation complete"
}

# --- Apply policies ---
do_apply() {
    section_header "Applying TracingPolicies"
    require_kubectl

    [[ -z "$POLICY_PATH" ]] && POLICY_PATH="${TETRAGON_POLICY_DIR:-}"
    [[ -z "$POLICY_PATH" ]] && die "No policy path specified. Use --policy DIR|FILE"

    local applied=0 failed=0
    local -a files=()

    if [[ -d "$POLICY_PATH" ]]; then
        while IFS= read -r -d '' f; do
            files+=("$f")
        done < <(find "$POLICY_PATH" -type f \( -name '*.yaml' -o -name '*.yml' \) -print0 | sort -z)
        [[ ${#files[@]} -eq 0 ]] && die "No YAML files found in ${POLICY_PATH}"
    elif [[ -f "$POLICY_PATH" ]]; then
        files=("$POLICY_PATH")
    else
        die "Policy path not found: ${POLICY_PATH}"
    fi

    for f in "${files[@]}"; do
        local basename_f
        basename_f=$(basename "$f")
        verbose "Validating ${basename_f}"

        if ! kubectl_cmd apply --dry-run=client -f "$f" >/dev/null 2>&1; then
            err "Validation failed: ${basename_f}"
            failed=$((failed + 1))
            continue
        fi

        verbose "Applying ${basename_f}"
        if kubectl_cmd apply -f "$f" -n "$NAMESPACE"; then
            applied=$((applied + 1))
            log "Applied: ${basename_f}"
        else
            err "Failed to apply: ${basename_f}"
            failed=$((failed + 1))
        fi
    done

    section_header "Apply Summary"
    field_color "Applied" "${GREEN}" "$applied"
    [[ $failed -gt 0 ]] && field_color "Failed" "${RED}" "$failed"

    verbose "Checking policy status"
    kubectl_cmd get tracingpolicy -n "$NAMESPACE" 2>/dev/null || true
}

# --- Audit policies ---
do_audit() {
    section_header "Auditing Tetragon Policies"
    require_kubectl

    log "Checking TracingPolicy resources"
    local tp_output
    tp_output=$(kubectl_cmd get tracingpolicy --all-namespaces -o json 2>/dev/null || echo '{"items":[]}')

    local tp_count
    tp_count=$(echo "$tp_output" | grep -c '"kind"' || true)
    field "TracingPolicies" "$tp_count found"

    if command -v jq >/dev/null 2>&1 && [[ "$tp_count" -gt 0 ]]; then
        echo "$tp_output" | jq -r '.items[] | "  \(.metadata.name) — sensors: \(.spec.kprobes // [] | length) kprobes, \(.spec.tracepoints // [] | length) tracepoints"' 2>/dev/null || true
    fi

    log "Checking ClusterTracingPolicy resources"
    local ctp_count
    ctp_count=$(kubectl_cmd get clustertracingpolicy --all-namespaces -o name 2>/dev/null | wc -l || echo 0)
    field "ClusterTracingPolicies" "${ctp_count} found"

    section_header "Tetragon Pod Health"
    local ds_json
    ds_json=$(kubectl_cmd get daemonset tetragon -n "$NAMESPACE" -o json 2>/dev/null || echo "")
    if [[ -n "$ds_json" ]] && command -v jq >/dev/null 2>&1; then
        local desired ready
        desired=$(echo "$ds_json" | jq '.status.desiredNumberScheduled // 0')
        ready=$(echo "$ds_json" | jq '.status.numberReady // 0')
        field "DaemonSet desired" "$desired"
        if [[ "$ready" -eq "$desired" ]]; then
            field_color "DaemonSet ready" "${GREEN}" "$ready"
        else
            field_color "DaemonSet ready" "${RED}" "${ready} (expected ${desired})"
        fi
    else
        kubectl_cmd get daemonset tetragon -n "$NAMESPACE" 2>/dev/null || warn "Tetragon DaemonSet not found"
    fi

    section_header "Common Misconfiguration Checks"
    local pods_not_ready
    pods_not_ready=$(kubectl_cmd get pods -n "$NAMESPACE" -l app.kubernetes.io/name=tetragon \
        --field-selector=status.phase!=Running -o name 2>/dev/null | wc -l || echo 0)
    if [[ "$pods_not_ready" -gt 0 ]]; then
        warn "${pods_not_ready} Tetragon pod(s) not in Running state"
    else
        log "All Tetragon pods are Running"
    fi

    log "Audit complete"
}

# --- Status ---
do_status() {
    section_header "Tetragon Status"
    require_kubectl

    local ds_output
    ds_output=$(kubectl_cmd get daemonset tetragon -n "$NAMESPACE" -o wide 2>/dev/null || echo "")
    if [[ -z "$ds_output" ]]; then
        warn "Tetragon DaemonSet not found in namespace ${NAMESPACE}"
        return
    fi
    echo "$ds_output"

    printf "\n"
    local pod_count
    pod_count=$(kubectl_cmd get pods -n "$NAMESPACE" -l app.kubernetes.io/name=tetragon \
        -o name 2>/dev/null | wc -l || echo 0)
    local ready_count
    ready_count=$(kubectl_cmd get pods -n "$NAMESPACE" -l app.kubernetes.io/name=tetragon \
        --field-selector=status.phase=Running -o name 2>/dev/null | wc -l || echo 0)
    field "Pods total" "$pod_count"
    field_color "Pods ready" "${GREEN}" "$ready_count"

    local policy_count
    policy_count=$(kubectl_cmd get tracingpolicy --all-namespaces -o name 2>/dev/null | wc -l || echo 0)
    field "Active policies" "$policy_count"

    section_header "Recent Tetragon Events (last 10)"
    kubectl_cmd get events -n "$NAMESPACE" --field-selector involvedObject.name=tetragon \
        --sort-by=.lastTimestamp 2>/dev/null | tail -n 10 || warn "No events found"
}

# --- Export ---
do_export() {
    section_header "Exporting TracingPolicies"
    require_kubectl

    mkdir -p "$OUTPUT_DIR"

    local policies
    policies=$(kubectl_cmd get tracingpolicy --all-namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)

    if [[ -z "$policies" ]]; then
        warn "No TracingPolicy resources found to export"
        return
    fi

    local count=0
    while IFS= read -r name; do
        [[ -z "$name" ]] && continue
        local outfile="${OUTPUT_DIR}/${name}.yaml"
        verbose "Exporting ${name} to ${outfile}"
        kubectl_cmd get tracingpolicy "$name" -o yaml > "$outfile"
        log "Exported: ${name} → ${outfile}"
        count=$((count + 1))
    done <<< "$policies"

    field_color "Total exported" "${GREEN}" "$count"
}

# --- Delete ---
do_delete() {
    section_header "Deleting TracingPolicies"
    require_kubectl

    if [[ "$DELETE_ALL" == "true" ]]; then
        if [[ "$CONFIRM_YES" != "true" ]]; then
            printf "Delete ALL TracingPolicies? [y/N] "
            read -r answer
            [[ "$answer" =~ ^[Yy]$ ]] || die "Aborted"
        fi
        local deleted
        deleted=$(kubectl_cmd delete tracingpolicy --all -n "$NAMESPACE" 2>&1)
        log "$deleted"
    elif [[ -n "$POLICY_PATH" ]]; then
        DELETE_NAME="$POLICY_PATH"
        if [[ "$CONFIRM_YES" != "true" ]]; then
            printf "Delete TracingPolicy '%s'? [y/N] " "$DELETE_NAME"
            read -r answer
            [[ "$answer" =~ ^[Yy]$ ]] || die "Aborted"
        fi
        kubectl_cmd delete tracingpolicy "$DELETE_NAME" -n "$NAMESPACE"
        log "Deleted TracingPolicy: ${DELETE_NAME}"
    else
        die "Specify --policy NAME or --all for deletion"
    fi
}

# --- Generate templates ---
generate_file_monitor() {
    cat <<'YAML'
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: file-monitor
spec:
  kprobes:
    - call: "security_file_open"
      syscall: false
      args:
        - index: 0
          type: "file"
      selectors:
        - matchArgs:
            - index: 0
              operator: "Prefix"
              values:
                - "/etc/shadow"
                - "/etc/passwd"
                - "/etc/sudoers"
                - "/etc/pam.d"
                - "/root/.ssh"
YAML
}

generate_process_exec() {
    cat <<'YAML'
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: process-exec
spec:
  kprobes:
    - call: "__x64_sys_execve"
      syscall: true
      args:
        - index: 0
          type: "string"
YAML
}

generate_network_connect() {
    cat <<'YAML'
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: network-connect
spec:
  kprobes:
    - call: "tcp_connect"
      syscall: false
      args:
        - index: 0
          type: "sock"
YAML
}

generate_privilege_escalation() {
    cat <<'YAML'
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: privilege-escalation
spec:
  kprobes:
    - call: "__x64_sys_setuid"
      syscall: true
      args:
        - index: 0
          type: "int"
    - call: "__x64_sys_setgid"
      syscall: true
      args:
        - index: 0
          type: "int"
    - call: "commit_creds"
      syscall: false
      args:
        - index: 0
          type: "cred"
YAML
}

generate_sensitive_mount() {
    cat <<'YAML'
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: sensitive-mount
spec:
  kprobes:
    - call: "__x64_sys_mount"
      syscall: true
      args:
        - index: 0
          type: "string"
        - index: 1
          type: "string"
      selectors:
        - matchArgs:
            - index: 1
              operator: "Prefix"
              values:
                - "/host"
                - "/var/run/docker.sock"
                - "/proc"
                - "/sys"
YAML
}

generate_crypto_mining() {
    cat <<'YAML'
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: crypto-mining
spec:
  kprobes:
    - call: "tcp_connect"
      syscall: false
      args:
        - index: 0
          type: "sock"
    - call: "__x64_sys_execve"
      syscall: true
      args:
        - index: 0
          type: "string"
      selectors:
        - matchArgs:
            - index: 0
              operator: "Postfix"
              values:
                - "xmrig"
                - "minerd"
                - "cpuminer"
                - "ethminer"
                - "cgminer"
                - "bfgminer"
YAML
}

do_generate() {
    [[ -z "$GENERATE_TEMPLATE" ]] && die "No template specified for --generate"

    case "$GENERATE_TEMPLATE" in
        file-monitor)           generate_file_monitor ;;
        process-exec)           generate_process_exec ;;
        network-connect)        generate_network_connect ;;
        privilege-escalation)   generate_privilege_escalation ;;
        sensitive-mount)        generate_sensitive_mount ;;
        crypto-mining)          generate_crypto_mining ;;
        *)                      die "Unknown template: ${GENERATE_TEMPLATE}. Use --help for available templates." ;;
    esac
}

# --- Main ---
main() {
    parse_args "$@"
    setup_colors

    verbose "Mode: ${MODE}"
    verbose "Format: ${OUTPUT_FORMAT}"
    verbose "Namespace: ${NAMESPACE}"
    [[ -n "$KUBECONFIG_FILE" ]] && verbose "Kubeconfig: ${KUBECONFIG_FILE}"
    [[ -n "$KUBE_CTX" ]] && verbose "Context: ${KUBE_CTX}"

    case "$MODE" in
        install)    do_install ;;
        apply)      do_apply ;;
        audit)      do_audit ;;
        status)     do_status ;;
        export)     do_export ;;
        delete)     do_delete ;;
        generate)   do_generate ;;
        *)          die "Unknown mode: ${MODE}" ;;
    esac
}

main "$@"
