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.
Sources
Section titled “Sources”| Source | Mechanism |
|---|---|
coredns-ugallu | A custom plugin in CoreDNS streams every query to the operator over gRPC. Authoritative, low-latency, in-line. |
tetragon | Fallback 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.
Detectors
Section titled “Detectors”- 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.
Topology
Section titled “Topology”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.
Example
Section titled “Example”apiVersion: security.ugallu.io/v1alpha1kind: DNSDetectConfigmetadata: { 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 }Internals
Section titled “Internals”State machine
Section titled “State machine”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.
Reconcile loop (config status)
Section titled “Reconcile loop (config status)”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: 30sError recovery
Section titled “Error recovery”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.
Crash recovery scenario
Section titled “Crash recovery scenario”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.
Edge cases
Section titled “Edge cases”- 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.
Full RBAC
Section titled “Full RBAC”# ClusterRolerules: - 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]CRDs owned
Section titled “CRDs owned”DNSDetectConfig- singleton; status carries the active source + per-detector counters.
Key flags
Section titled “Key flags”--cluster-id, --cluster-name, --config-name (default
default), --resolver-endpoint, --resolver-uds,
--resolver-insecure, --resolver-disable.
Deployment
Section titled “Deployment”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.
Telemetry
Section titled “Telemetry”ugallu_dns_events_total{source},
ugallu_dns_detector_fires_total{detector},
ugallu_dns_resolver_lookups_total{outcome},
ugallu_dns_inflight_lookups.