Files
linux-scripts/tetragon-policy-deployer.sh
T
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

606 lines
19 KiB
Bash
Executable File

#!/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 "$@"