Get started

Kubernetes security collector

om scan k8s uses KUBECONFIG when that variable is set, otherwise the default kubeconfig (~/.kube/config). In a pod, with KUBECONFIG unset, it uses the pod service account. K8S_CLUSTER defaults to default and is the account id on every node. When a kubeconfig is used, any other value is also the context name. In-cluster, that value is only the account id. K8S_NAMESPACE limits the scan to one namespace. Empty means every namespace the identity can list.

export K8S_CLUSTER=prod
export K8S_NAMESPACE=payments
./om scan k8s
./om rules run

The identity needs list on namespaces (unless K8S_NAMESPACE is set), pods, services, and service accounts. Deployments and ReplicaSets are not nodes. Pods are the workloads. Each pod stores images: the sorted, unique image refs from its init containers and app containers. Ephemeral containers are omitted. An empty image list is not written.

Mapping

Object Node
Cluster Control named with K8S_CLUSTER. Id k8s:{cluster}:control:cluster.
Namespace Network with namespace. Id k8s:{cluster}:network:{namespace}.
Pod Workload with namespace, phase, node, and images when the pod has container image refs. Id k8s:{cluster}:workload:{namespace}/{pod}.
ServiceAccount Identity with namespace. Id k8s:{cluster}:identity:{namespace}/{name}.
Service Network with namespace, service_type, and internet_facing. Id k8s:{cluster}:network:{namespace}/service/{name}.

internet_facing is true for LoadBalancer and NodePort. ClusterIP and ExternalName are recorded and are not treated as internet-facing. The collector does not check whether a load balancer actually has an external address.

Edges

  • AFFECTS from a pod to its namespace node.
  • ASSUMES from the pod’s service account to the pod. The service account is the source. An empty serviceAccountName is treated as default, which is the opposite direction from the AWS instance-profile edge (workload to role).
  • AFFECTS from a pod to a Service in the same namespace when the Service selector matches the pod labels. An empty selector matches nothing. The edge property via is service selector.
  • REACHABLE from internet:global to a pod when at least one matching Service is LoadBalancer or NodePort. The edge property via is that Service’s name. The first public Service wins if several match.

A public Service with no matching pods does not create a REACHABLE edge. Pods behind only ClusterIP stay off attack paths that start at the internet, even when an Ingress would expose them.

Pack rule that does not see this scan

k8s-public-loadbalancer matches workloads whose property k8s_service_type is LoadBalancer and that are internet-reachable. This collector writes service_type on the Service’s Network node. It does not copy that property onto the pod. A live om scan k8s therefore does not fire that rule. The demo graph sets k8s_service_type on a workload so the rule has a sample. Internet-exposed pods from a real cluster still match cspm-internet-workload, because that rule follows REACHABLE edges.