Skip to content

dns-detect

ugallu-dns-detect watches DNS traffic on every node and fires SecurityEvents when a query crosses a configured detector. Two sources, one detector chain.

SourceMechanism
coredns-ugalluA custom plugin in CoreDNS streams every query to the operator over gRPC. Authoritative, low-latency, in-line.
tetragonFallback kprobe on udp_sendmsg / tcp_sendmsg with a UDP/53 + TCP/853 filter. Useful on managed control planes where you can’t deploy a CoreDNS plugin.

The active source surfaces on DNSDetectConfig.status.activeSource, along with a count of in-flight lookups.

  • blocklist - domain matches a curated allowlist + per-namespace blocklist ConfigMap. Hot-reloaded.
  • dga - Shannon entropy + n-gram score over the queried label flags algorithmically generated domains.
  • slow-query - per-resolver latency outlier; surfaces exfiltration-via-DNS attempts that pad records to slow throughput.
  • doh / dot - flags egress to known DNS-over-HTTPS / DNS-over-TLS resolvers (Cloudflare 1.1.1.1, Google 8.8.4.4, …).

Each detector is independently toggle-able via DNSDetectConfig.

DaemonSet (one pod per node) for capture; leader election picks a single dispatcher to write SE CRs so 100 nodes don’t race on Create. Resolver enrichment (Pod / Namespace lookup from query SrcIP) is best-effort - if the resolver is unreachable, the SE goes out with a synthetic SrcIP key rather than blocking detection.

apiVersion: security.ugallu.io/v1alpha1
kind: DNSDetectConfig
metadata: { name: default, namespace: ugallu-system }
spec:
source:
mode: coredns-ugallu
detectors:
blocklist:
enabled: true
namespaceConfigMaps:
- { namespace: ops, name: dns-blocklist }
dga: { enabled: true, minLength: 12, minEntropy: 3.5 }
slowQuery: { enabled: true, latencyP99: 500ms }
doh: { enabled: true }
dot: { enabled: false }

DNSDetectConfig is a singleton with no phase - the reconciler refreshes derived status fields on a 30s tick. The capture + detector chain runs as a manager Runnable, independent of the reconciler.

on each DNSDetectConfig event or 30s tick:
cfg := Get("default")
if cfg missing: # pre-install gap
RequeueAfter: 30s; return
patch Status.Source = currentActiveSource()
patch Status.InflightLookups = resolverInflight()
patch Status.LastConfigLoadAt = now
RequeueAfter: 30s

Source disconnect -> exponential backoff retry on the gRPC dial. After 60s of silence the operator emits DNSSourceSilent (Anomaly) and switches to the configured fallback source if any. Status writes are best-effort and never block packet processing.

Pod killed during a backoff retry: new pod re-establishes the gRPC stream from scratch. No state loss - the detector chain is stateless per event.

  • Single dispatcher. Leader election ensures only one DaemonSet pod creates SE CRs, even though every pod captures.
  • Resolver enrichment is best-effort. If the resolver service is unreachable, SEs ship with a synthetic SrcIP key rather than blocking detection.
  • Per-namespace blocklist ConfigMaps are hot-reloaded.
  • YoungDomain detector uses RDAP, rate-limited at the detector level so a flood of new domains can’t melt the upstream registry.
# ClusterRole
rules:
- apiGroups: [security.ugallu.io]
resources: [dnsdetectconfigs, dnsdetectconfigs/status]
verbs: [get, list, watch, update, patch]
- apiGroups: [security.ugallu.io]
resources: [securityevents]
verbs: [create]
- apiGroups: [""]
resources: [events]
verbs: [create, patch]
# Namespaced Role(s) per blocklist source
- apiGroups: [""]
resources: [configmaps]
verbs: [get, list, watch]
# Namespaced Role for the bridge token Secret
- apiGroups: [""]
resources: [secrets]
resourceNames: [ugallu-coredns-grpc-token]
verbs: [get, list, watch]
# Leases for leader election
- apiGroups: [coordination.k8s.io]
resources: [leases]
verbs: [get, list, watch, create, update, patch, delete]
  • DNSDetectConfig - singleton; status carries the active source + per-detector counters.

--cluster-id, --cluster-name, --config-name (default default), --resolver-endpoint, --resolver-uds, --resolver-insecure, --resolver-disable.

DaemonSet in ugallu-system, leader election on (single dispatcher). HOSTNAME env propagated as the CoreDNS subscriber ID so the plugin can route per-node streams. Reads the coredns-ugallu Secret to authenticate with the plugin.

ugallu_dns_events_total{source}, ugallu_dns_detector_fires_total{detector}, ugallu_dns_resolver_lookups_total{outcome}, ugallu_dns_inflight_lookups.