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
AFFECTSfrom a pod to its namespace node.ASSUMESfrom the pod’s service account to the pod. The service account is the source. An emptyserviceAccountNameis treated asdefault, which is the opposite direction from the AWS instance-profile edge (workload to role).AFFECTSfrom a pod to a Service in the same namespace when the Service selector matches the pod labels. An empty selector matches nothing. The edge propertyviaisservice selector.REACHABLEfrominternet:globalto a pod when at least one matching Service isLoadBalancerorNodePort. The edge propertyviais 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.
Copyright © 2026 OpenSourceOM. Licensed under Apache-2.0.