Security

Runtime Enforcement

Kernel-level runtime enforcement using Cilium Tetragon. Provides the third layer of Lattice's defense-in-depth model alongside L4 network policies and L7 authorization policies.

powered by: Tetragon api: cilium.io/v1alpha1 mechanism: eBPF LSM hooks

Defense in Depth

Lattice enforces security at three layers. Runtime enforcement adds kernel-level protection that cannot be bypassed from userspace, even if network and application controls are compromised.

L4 Network

Cilium NetworkPolicies control pod-to-pod connectivity at the network layer.

L7 Application

Istio AuthorizationPolicies enforce identity-based access at the application layer.

Kernel Runtime

Tetragon TracingPolicies attach eBPF programs to LSM hooks, blocking dangerous operations at the kernel.

Policy Tiers

Tier 1 Cluster Baseline (cluster-scoped TracingPolicy)

Installed at cluster bootstrap. Unconditionally blocks dangerous syscalls across all workload namespaces.

ptrace — anti-debugging / container escape
init_module / finit_module — kernel modules
mount / umount — filesystem manipulation
unshare / setns — namespace escape
Writes to /etc/shadow, /etc/passwd, /etc/sudoers

Excluded namespaces: kube-system, cilium-system, istio-system, lattice-system, cert-manager.

Tier 1b Binary Allowlist (per-service TracingPolicyNamespaced)

Generated per-service by the compiler. Only binaries in the final allowlist may execute — everything else receives SIGKILL via the security_bprm_check LSM hook. The allowlist is the union of explicitly declared allowedBinaries and auto-detected entrypoints from container commands and exec probes.

If a container has no command and no allowedBinaries, the image entrypoint is unknown — the compiler grants an implicit wildcard and no binary policy is generated. Use allowedBinaries: ["*"] to explicitly disable binary restrictions.

Tier 2 Security Context Enforcement (per-service TracingPolicyNamespaced)

Derived from your LatticeService security context. The compiler generates enforcement policies that match your declared security posture.

readOnlyRootFilesystem: true Blocks open() with write flags on non-tmpfs
runAsNonRoot: true Blocks setuid / setgid syscalls
capabilities.drop: [ALL] Blocks capset entirely

How It Works

Runtime enforcement policies are automatically generated during service compilation. The compiler reads your service's security context and generates the appropriate Tetragon TracingPolicies.

1 You declare security context in your LatticeService spec (allowedBinaries, readOnlyRootFilesystem, runAsNonRoot, capabilities). The compiler also scans command and exec probe entrypoints.
2 The service compiler generates TracingPolicyNamespaced resources scoped to your service's pods.
3 Tetragon attaches eBPF programs to Linux Security Module hooks in the kernel.
4 Blocked operations receive SIGKILL — the process is terminated immediately.

allowedBinaries

The allowedBinaries field in your container's security context declares which binaries your service needs to execute. The compiler builds a final allowlist from the union of declared binaries and auto-detected entrypoints, then generates an allow-binaries TracingPolicy that kills any process not on the list.

allowedBinaries behavior
Value command Behavior
allowedBinaries: ["/usr/bin/app", ...] any Only listed binaries plus auto-detected entrypoints may execute. Everything else is killed.
(not set) declared Policy generated. Only command[0] and exec probe entrypoints may execute.
(not set) (not set) Implicit wildcard. Image entrypoint is unknown — no binary policy generated.
allowedBinaries: ["*"] any Explicit wildcard. No binary restrictions. The allow-binaries policy is not generated.

Policies are pod-scoped. For multi-container pods, allowedBinaries lists and auto-detected entrypoints from all containers and sidecars are merged into one union allowlist. If any container uses the * wildcard or has an unknown entrypoint (no command and no allowedBinaries), binary restrictions are disabled for the entire pod.

Cedar Authorization

Every allowedBinary entry — whether explicit or implicit — requires authorization from Cedar policy before the service can compile. The compiler generates a SecurityOverrideRequest for each binary, and Cedar must return a permit decision. This is default-deny: without a matching Cedar policy, compilation fails.

Cedar authorization requests generated by allowedBinaries
Scenario Cedar Override Generated
allowedBinaries: ["/usr/bin/app"] allowedBinary:/usr/bin/app
allowedBinaries: ["*"] allowedBinary:*
Implicit wildcard (no command, no allowedBinaries) allowedBinary:*

Both implicit and explicit wildcards require allowedBinary:* to be permitted by Cedar. If your Cedar policy only permits specific binary paths, a wildcard allowlist will be rejected. This ensures that platform teams retain control over which services can run arbitrary binaries, even when workload authors omit binary restrictions.

Generated Policies

For a service with a fully hardened security context, the compiler generates four TracingPolicyNamespaced resources:

Generated policy names and triggers
Policy Condition LSM Hook
allow-binaries-{name} Unless * wildcard or unknown entrypoint security_bprm_check
block-rootfs-write-{name} readOnlyRootFilesystem: true security_file_open
block-setuid-{name} runAsNonRoot: true security_task_fix_setuid
block-capset-{name} capabilities is empty or restricted security_capset

LSM Hooks

All hooks use architecture-independent Linux Security Module (LSM) functions, not arch-specific syscall numbers.

LSM hooks used by Lattice runtime enforcement
Hook Purpose Tier
security_ptrace_access_check Block process debugging 1
security_kernel_module_request Block kernel module loading 1
security_sb_mount Block mount operations 1
security_sb_umount Block unmount operations 1
security_bprm_check Binary execution allowlist 1b
security_file_open Block file access (rootfs writes) 1 + 2
security_task_fix_setuid Block UID/GID changes 2
security_capset Block capability changes 2

Examples

Fully hardened service with explicit binary allowlist:

api-server.yaml
apiVersion: lattice.dev/v1alpha1
kind: LatticeService
metadata:
  name: api-server
  namespace: production
spec:
  workload:
    containers:
      - name: app
        image: api-server:latest
        securityContext:
          readOnlyRootFilesystem: true    # → block-rootfs-write policy
          runAsNonRoot: true              # → block-setuid policy
          capabilities:
            drop: [ALL]                   # → block-capset policy
          allowedBinaries:                    # → allow-binaries policy
            - /usr/bin/api-server
            - /usr/bin/curl

PHP app that shells out to ImageMagick and ffmpeg — declare the binaries you need, and exec probe entrypoints are auto-detected:

image-processor.yaml
apiVersion: lattice.dev/v1alpha1
kind: LatticeService
metadata:
  name: image-processor
  namespace: media
spec:
  workload:
    containers:
      - name: php
        image: image-processor:latest
        securityContext:
          allowedBinaries:
            - /usr/sbin/php-fpm
            - /usr/bin/convert          # ImageMagick
            - /usr/bin/ffmpeg
            - /usr/bin/curl             # used by probe
        livenessProbe:
          exec:
            command: ["/bin/sh", "-c", "curl -f localhost:9000/health"]
        # /bin/sh auto-detected from probe command[0], curl explicitly allowed

Generated allow-binaries policy for the image-processor (uses NotEqual — anything not in the list is killed). Note /bin/sh auto-detected from the exec probe:

allow-binaries-image-processor.yaml
apiVersion: cilium.io/v1alpha1
kind: TracingPolicyNamespaced
metadata:
  name: allow-binaries-image-processor
  namespace: media
  labels:
    lattice.dev/managed-by: lattice
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: image-processor
  kprobes:
    - call: security_bprm_check
      syscall: false
      args:
        - index: 0
          type: file
      selectors:
        - matchArgs:
            - index: 0
              operator: NotEqual       # SIGKILL anything NOT in this list
              values:
                - /usr/sbin/php-fpm
                - /usr/bin/convert
                - /usr/bin/ffmpeg
                - /usr/bin/curl
                - /bin/sh            # auto-detected from probe
          matchActions:
            - action: Sigkill

CI runner that needs unrestricted binary execution:

build-runner.yaml
apiVersion: lattice.dev/v1alpha1
kind: LatticeService
metadata:
  name: build-runner
  namespace: ci
spec:
  workload:
    containers:
      - name: runner
        image: ci-runner:latest
        securityContext:
          allowedBinaries: ["*"]        # disable binary restrictions entirely

Cluster-wide baseline policy (installed automatically at bootstrap):

lattice-baseline-runtime.yaml
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: lattice-baseline-runtime
spec:
  kprobes:
    - call: security_ptrace_access_check
      syscall: false
      args:
        - index: 1
          type: int
      selectors:
        - matchNamespaces:
            - namespace: Namespace
              operator: NotIn
              values:
                - kube-system
                - cilium-system
                - istio-system
                - lattice-system
                - cert-manager
          matchActions:
            - action: Sigkill

Entrypoint Auto-detection

The compiler extracts command[0] from every container command and exec probe (liveness, readiness, startup) across all containers and sidecars. Only the binary path is taken — arguments are ignored. HTTP probes are not scanned since they use native network calls. The final allowlist is the union of all declared allowedBinaries and all auto-detected entrypoints.

Multi-Container Aggregation

Tetragon policies are pod-scoped, not container-scoped. For pods with multiple containers or sidecars, the compiler aggregates using the most permissive setting across all containers:

  • allowedBinaries and auto-detected entrypoints are merged into a single union allowlist.
  • If any container uses the * wildcard, binary restrictions are disabled for the entire pod.
  • If any container has an unknown entrypoint (no command and no allowedBinaries), the pod gets an implicit wildcard.
  • If any container sets readOnlyRootFilesystem: false, the rootfs write policy is skipped for the entire pod.