cli: add homelab edges — who-talks-to-whom investigation helper (v0.9.0)
Makes the goldmane_edges east-west trail (ADR-0014) reachable during incident investigations without remembering the DB/creds/SQL. New top-level verb: homelab edges --ns <ns> edges touching <ns> (either direction) homelab edges --src/--dst <ns> directional egress / ingress peers homelab edges --peers-of <ns> distinct peer namespaces of <ns> homelab edges --new-since 24h first seen since a duration or date (YYYY-MM-DD) homelab edges --denied only action='deny' (blocked / lateral movement) homelab edges --json --limit N machine-readable / row cap (default 200) Filters render to a single read-only SELECT against the `edge` table, run via the dbaas CNPG primary pod (same exec path as `k8s db`). Namespace values are validated to the k8s name charset (injection guard) before they reach SQL. TDD: edges_test.go covers flag parsing, query building (each filter, AND combination, peers-of shape, JSON wrapper), the new-since duration/date parser, and namespace-validation / injection rejection. Smoke-tested live: --peers-of, --new-since 24h, --denied, and --json all return correct rows. Docs: runbook query section now leads with the CLI; cli/README gains a v0.9 section. VERSION v0.8.2 -> v0.9.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0fa5852ec6
commit
9a1ab6247b
7 changed files with 429 additions and 3 deletions
69
cli/cmd_edges.go
Normal file
69
cli/cmd_edges.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func edgesCommands() []Command {
|
||||
return []Command{
|
||||
{Path: []string{"edges"}, Tier: TierRead,
|
||||
Summary: "who-talks-to-whom trail: edges [--ns|--src|--dst|--peers-of N] [--new-since 24h] [--denied] [--json] [--limit N]",
|
||||
Run: edgesRun},
|
||||
}
|
||||
}
|
||||
|
||||
// edgesRun renders the filter flags to SQL and runs it read-only against the
|
||||
// goldmane_edges CNPG DB via the dbaas primary pod (same exec path as `k8s db`).
|
||||
func edgesRun(args []string) error {
|
||||
for _, a := range args {
|
||||
if a == "-h" || a == "--help" {
|
||||
fmt.Print(edgesUsage())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
o, err := parseEdgesArgs(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w\n\n%s", err, edgesUsage())
|
||||
}
|
||||
sql, err := buildEdgesQuery(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// pg-cluster-rw is a Service (not exec-able); resolve the primary POD.
|
||||
pod, err := kubectlCapture("dbaas", "get", "pod", "-l", "cnpg.io/instanceRole=primary",
|
||||
"-o", "jsonpath={.items[0].metadata.name}")
|
||||
if err != nil || pod == "" {
|
||||
return fmt.Errorf("could not resolve CNPG primary pod in dbaas: %v", err)
|
||||
}
|
||||
exec := []string{"exec", pod, "-c", "postgres", "--", "psql", "-U", "postgres", "-d", "goldmane_edges"}
|
||||
if o.asJSON {
|
||||
exec = append(exec, "-tAc", sql) // raw tuple → the JSON array
|
||||
} else {
|
||||
exec = append(exec, "-P", "pager=off", "-c", sql) // aligned table for humans
|
||||
}
|
||||
return kubectlStream("dbaas", exec...)
|
||||
}
|
||||
|
||||
func edgesUsage() string {
|
||||
return `homelab edges — query the who-talks-to-whom trail (goldmane_edges, ADR-0014)
|
||||
|
||||
Usage: homelab edges [filters]
|
||||
|
||||
Filters (AND-combined; namespace values are validated to the k8s name charset):
|
||||
--ns NAME edges touching NAME (either direction)
|
||||
--src NAME edges where source namespace = NAME
|
||||
--dst NAME edges where destination namespace = NAME
|
||||
--peers-of NAME distinct peer namespaces of NAME (both directions)
|
||||
--new-since SPEC first seen since SPEC: a duration (24h, 7d, 30m, 90s) or a date (YYYY-MM-DD)
|
||||
--denied only denied (action='deny') edges — blocked / lateral-movement attempts
|
||||
--json output a JSON array (for agents/pipelines)
|
||||
--limit N cap rows (default 200)
|
||||
|
||||
Examples:
|
||||
homelab edges --ns immich # everything immich talks to / is talked to by
|
||||
homelab edges --peers-of authentik # authentik's peer namespaces
|
||||
homelab edges --src recruiter-responder # that namespace's egress peers
|
||||
homelab edges --new-since 24h # edges first seen in the last day
|
||||
homelab edges --denied --json # blocked flows, machine-readable
|
||||
|
||||
Read-only SELECT against CNPG DB goldmane_edges via the dbaas primary pod.
|
||||
`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue