Skip to content

Runtime security with Tetragon

Tetragon is Cilium’s open-source eBPF runtime security agent. It watches the kernel on each node and reports what processes do: every process start, and every match of a TracingPolicy you apply (a file read, a privilege change, a network connection). Shield360 reads those events from each node, shows them with your logs, and raises detections from them.

What you get:

  • Every process start in your pods and on your nodes: the binary, its arguments, its parent, and the pod, container, image and workload it ran in.
  • Policy matches from Shield360’s baseline policies (below), or your own.
  • Detections, ready to turn on: Sensitive file read on a node, Privilege escalation on a node, Shell started in a container and Burst of outbound connections from a node.
  1. Install Tetragon with its Helm chart, as the Tetragon docs describe:

    Terminal window
    helm repo add cilium https://helm.cilium.io
    helm install tetragon cilium/tetragon -n kube-system

    It writes its events to /var/run/cilium/tetragon/tetragon.log on each node.

  2. Apply Shield360’s baseline policies. Your Shield360 server serves all three as one YAML file (replace https://app.shield360.ai with your own address if you self-host):

    Terminal window
    kubectl apply -f https://app.shield360.ai/api/downloads/tetragon-policies

    To apply only some, name them with policy (comma-separated):

    Terminal window
    kubectl apply -f "https://app.shield360.ai/api/downloads/tetragon-policies?policy=shield360-sensitive-files,shield360-privilege-escalation"
    PolicyReports
    shield360-sensitive-filesReads of /etc/shadow, /etc/gshadow, /etc/sudoers and root’s SSH, AWS, Kubernetes and gcloud credentials, except by sshd, sudo, su, passwd and the PAM password checker
    shield360-privilege-escalationsetuid(0) and setgid(0), except by sudo, su, login, sshd, cron and systemd
    shield360-outbound-connectionsTCP connections to public addresses, and to the cloud metadata address. Private, loopback and link-local ranges are left out

    Review a policy before you apply it, or save it and apply your own copy:

    shield360-sensitive-files
    # SHIELD: runtime security — reads of protected files on the node or in any pod.
    # Reported as process_kprobe events with policy_name shield360-sensitive-files;
    # Shield360's "Sensitive file read on a node" detection fires on any match.
    # The tools that read these files as part of their job (sshd, sudo, su,
    # passwd, the PAM password checker) are left out. Service-account tokens are
    # not watched by default: every app that talks to the Kubernetes API reads
    # its own at start; add the path if you want those reads reported too.
    apiVersion: cilium.io/v1alpha1
    kind: TracingPolicy
    metadata:
    name: shield360-sensitive-files
    spec:
    kprobes:
    - call: security_file_permission
    syscall: false
    return: true
    args:
    - index: 0
    type: file
    - index: 1
    type: int
    returnArg:
    index: 0
    type: int
    returnArgAction: Post
    selectors:
    - matchArgs:
    - index: 0
    operator: Prefix
    values:
    - /etc/shadow
    - /etc/gshadow
    - /etc/sudoers
    - /root/.ssh
    - /root/.aws
    - /root/.kube
    - /root/.config/gcloud
    - index: 1
    operator: Mask
    values:
    - "4" # MAY_READ
    matchBinaries:
    - operator: NotIn
    values:
    - /usr/sbin/sshd
    - /usr/bin/sudo
    - /usr/bin/su
    - /bin/su
    - /usr/bin/passwd
    - /usr/sbin/unix_chkpwd
    shield360-privilege-escalation
    # SHIELD: runtime security — a process becoming root. Reported as
    # process_kprobe events with policy_name shield360-privilege-escalation;
    # Shield360's "Privilege escalation on a node" detection fires on any match.
    # setuid(0) and setgid(0) by the tools whose job that is (sudo, su, login,
    # sshd, cron, systemd) are left out.
    apiVersion: cilium.io/v1alpha1
    kind: TracingPolicy
    metadata:
    name: shield360-privilege-escalation
    spec:
    kprobes:
    - call: sys_setuid
    syscall: true
    args:
    - index: 0
    type: int
    selectors:
    - matchArgs:
    - index: 0
    operator: Equal
    values:
    - "0"
    matchBinaries: &trusted
    - operator: NotIn
    values:
    - /usr/bin/sudo
    - /usr/bin/su
    - /bin/su
    - /usr/bin/login
    - /bin/login
    - /usr/sbin/sshd
    - /usr/sbin/cron
    - /usr/sbin/crond
    - /usr/lib/systemd/systemd
    - /lib/systemd/systemd
    - call: sys_setgid
    syscall: true
    args:
    - index: 0
    type: int
    selectors:
    - matchArgs:
    - index: 0
    operator: Equal
    values:
    - "0"
    matchBinaries: *trusted
    shield360-outbound-connections
    # SHIELD: runtime security — TCP connections to public addresses. Reported as
    # process_kprobe events with policy_name shield360-outbound-connections, with
    # the process, pod and destination; Shield360's "Burst of outbound connections
    # from a node" detection fires at 50 in 15 minutes. Private, loopback and
    # link-local ranges are left out except the cloud metadata address
    # (169.254.169.254), which a workload reading credentials from is worth seeing.
    # A busy cluster sends many: start with it, then narrow it to the namespaces
    # that should not talk to the internet (selectors: matchNamespaces).
    apiVersion: cilium.io/v1alpha1
    kind: TracingPolicy
    metadata:
    name: shield360-outbound-connections
    spec:
    kprobes:
    - call: tcp_connect
    syscall: false
    args:
    - index: 0
    type: sock
    selectors:
    - matchArgs:
    - index: 0
    operator: NotDAddr
    values:
    - 127.0.0.0/8
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    - 100.64.0.0/10
    - ::1/128
    - fc00::/7
    - fe80::/10
  3. Turn it on in the Shield360 operator chart, with system logs on:

    Terminal window
    helm upgrade --install shield360 oci://ghcr.io/thinkfleetai/charts/shield360-operator \
    --version 0.3.0 -n shield360-system \
    --reuse-values --set systemLogs.enabled=true --set systemLogs.node.tetragon.enabled=true

    The node agent mounts Tetragon’s export directory read-only and reads tetragon.log. If your Tetragon chart uses another exportDirectory, set systemLogs.node.tetragon.exportDirectory to match.

  4. Turn on the detections in Detections: the four Tetragon templates are off until you enable them.

  • Logs: pick Source: Tetragon (runtime security). Process starts are INFO and read as the command line (/usr/bin/curl -s https://example.com/x.sh); policy matches are WARN and read as the policy, the hook, the file and the binary (shield360-sensitive-files security_file_permission /etc/shadow /usr/bin/cat). Each record carries tetragon.event, tetragon.policy, process.executable.path, process.command_args, process.parent.executable.path, k8s.namespace.name, k8s.pod.name, k8s.container.name, container.image.name and file.path as fields, so the facets and search work on them.
  • Detections: the templates read the same records, grouped by node or by workload.
  • Monitor → Detections → Runtime events (source Tetragon) and All detections: every policy match, with its policy, hook, action (reported, killed, call refused), file, binary, parent, pod, container, image, workload and node, filtered by policy, action, namespace, pod, binary or node, and counted over time by action. Process starts stay in Logs. The command line is not shown there; Open in Logs finds the event, masked like every log. Reading them needs Data protection access.

Process exits are left out (about half of Tetragon’s volume, and nothing to act on). Command lines go through the same masking as every log, so a password on a command line is masked before it is stored.

Every process start is an event. A cluster that starts many short-lived processes (shell scripts in a loop, say) sends many. The outbound connections policy is the noisiest: once you know your normal traffic, narrow it to the namespaces that should not reach the internet with a matchNamespaces selector, or remove it.