-
Notifications
You must be signed in to change notification settings - Fork 2
dx_evidence_graph: viz stub — coordination with dx-agent data model #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| # dx_evidence_graph | ||
|
|
||
| A Pixie UI dashboard that renders one dx-agent investigation as a | ||
| **severity-weighted, all-protocol pod-to-pod attack graph**. Replaces | ||
| the latency-weighted HTTP service map in `cluster_overview` for | ||
| security work. | ||
|
|
||
| * Nodes = pods. Falls back to service → IP, mirroring `net_flow_graph`. | ||
| * Edges = the attack path emitted by dx (delivery → egress → | ||
| execution → collection → exfil → pivot). | ||
| * Display spec: `vispb.Graph`. **`edgeWeightColumn = weight`** | ||
| (open-ended UInt16 sum of CRS severity → edge thickness), | ||
| **`edgeColorColumn = max_severity`** (discrete 2-5 heat → edge | ||
| colour). | ||
| * Read source: `forensic_db.dx_attack_graph` via `px.DataFrame`'s | ||
| `clickhouse_dsn` kwarg (`src/carnot/planner/objects/dataframe.cc:43`). | ||
|
|
||
| ## Schema — `forensic_db.dx_attack_graph` | ||
|
|
||
| Locked with dx-agent in PR #62 / `entlein/dx#68`. The | ||
| `attackgraph.Edge` Go struct is the single source of truth for the | ||
| JSON wire format, the ClickHouse row, and the test fixture. | ||
|
|
||
| | Column | Type | Role | | ||
| |---|---|---| | ||
| | `investigation_id` | String | one graph per dx verdict / pivot incident (UI filter key) | | ||
| | `ts` | UInt64 | unix nanos | | ||
| | `requestor_pod` / `responder_pod` | String | the hop (`ns/pod`); `""` if only an IP is known | | ||
| | `requestor_service` / `responder_service` | String | | | ||
| | `requestor_ip` / `responder_ip` | String | peer IP when pod unresolved | | ||
| | `weight` | UInt16 | Σ CRS severity on the hop — `edgeWeightColumn` | | ||
| | `max_severity` | UInt8 | top single-criterion severity (2-5) — `edgeColorColumn` | | ||
| | `confidence` | Float32 | verdict confidence | | ||
| | `edge_kind` | String | `delivery`/`egress`/`execution`/`collection`/`exfil`/`pivot` | | ||
| | `condition` / `criteria` | String | ruled-in condition + criterion label(s) | | ||
| | `num_findings` | UInt32 | | | ||
|
|
||
| Table DDL (mirrors `kubescape_logs` partition/TTL convention): | ||
|
|
||
| ```sql | ||
| CREATE TABLE forensic_db.dx_attack_graph ( ...columns above... ) | ||
| ENGINE = MergeTree | ||
| PARTITION BY toYYYYMM(fromUnixTimestamp64Nano(ts)) | ||
| ORDER BY (investigation_id, requestor_pod, responder_pod) | ||
| TTL toDateTime(fromUnixTimestamp64Nano(ts)) + INTERVAL 30 DAY DELETE; | ||
| ``` | ||
|
|
||
| ## Per-rig ClickHouse DSN | ||
|
|
||
| The bundled `vis.json` ships with `clickhouse_dsn` **empty** — the | ||
| default is intentionally non-credentialed so the bundle stays | ||
| portable across clusters. Operators fill the DSN in via the Pixie | ||
| UI script-args panel at run time. | ||
|
|
||
| For the in-cluster soc deployment the DSN is: | ||
|
|
||
| ``` | ||
| forensic_analyst:changeme-analyst@clickhouse-forensic-soc-db.clickhouse.svc.cluster.local:9000/forensic_db | ||
| ``` | ||
|
Comment on lines
+57
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a language tag to the fenced DSN block. Line 57 opens a fenced code block without a language, which violates MD040 and may fail markdown lint gates. Suggested fix-```
+```text
forensic_analyst:changeme-analyst@clickhouse-forensic-soc-db.clickhouse.svc.cluster.local:9000/forensic_dbVerify each finding against current code. Fix only still-valid issues, skip the In Source: Linters/SAST tools |
||
|
|
||
| `forensic_analyst` has read-only SELECT on `forensic_db`; same | ||
| credential the existing `soc/analysis/px_clickhouse/kubescape/observe.pxl` | ||
| script uses for `kubescape_logs`. Override in the UI for other rigs. | ||
|
|
||
| ## Deploy | ||
|
|
||
| Bundle build path: | ||
|
|
||
| 1. `//src/pxl_scripts:script_bundle` walks every `*.pxl` + `vis.json` | ||
| under `src/pxl_scripts/` and emits `bundle-oss.json` | ||
| (`src/pxl_scripts/BUILD.bazel:34`). | ||
| 2. `//src/cloud/proxy:proxy_server_image` bakes the bundle in as a | ||
| container layer at `/bundle` | ||
| (`src/cloud/proxy/BUILD.bazel:36`). | ||
| 3. `skaffold run -f skaffold/skaffold_cloud.yaml` rebuilds the | ||
| cloud-proxy image and applies the Deployment. | ||
|
|
||
| Vizier / PEM / standalone-pem images are unaffected — this is a | ||
| UI-bundle-only change. | ||
|
|
||
| ## Out of scope for v1 | ||
|
|
||
| * `conn_stats` overlay (the "render the benign neighbourhood + light | ||
| up the attack path" view). Ship the attack-path-only graph first; | ||
| add the join in v2 once the visual has been used on a real | ||
| incident. | ||
| * Time anchoring relative to `ts` rather than free-form `start_time`. | ||
| Operators today use `-15m` defaults; a future widget could centre | ||
| the window on the investigation's first `ts`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Copyright 2018- The Pixie Authors. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import px | ||
|
|
||
|
|
||
| def dx_attack_graph(start_time: str, clickhouse_dsn: str, table: str): | ||
| df = px.DataFrame(table, clickhouse_dsn=clickhouse_dsn, start_time=start_time) | ||
| # Node identity: pod, else service, else IP. Edges whose peer is an IP or a | ||
| # non-pod entity (the k8s API server, an external endpoint, a consulted | ||
| # socket) have an empty *_pod; keying the graph on *_pod alone collapses ALL | ||
| # of them into one bogus "" node. Coalesce so each distinct peer is its own | ||
| # node — same idiom Pixie's net_flow_graph uses (px.select(src=='', src_ip, src)). | ||
| df.requestor = px.select(df.requestor_pod == '', | ||
| px.select(df.requestor_service == '', df.requestor_ip, df.requestor_service), | ||
| df.requestor_pod) | ||
| df.responder = px.select(df.responder_pod == '', | ||
| px.select(df.responder_service == '', df.responder_ip, df.responder_service), | ||
| df.responder_pod) | ||
| return df[['requestor', 'responder', | ||
| 'requestor_pod', 'responder_pod', | ||
| 'requestor_service', 'responder_service', | ||
| 'requestor_ip', 'responder_ip', | ||
| 'weight', 'max_severity', 'confidence', | ||
| 'edge_kind', 'condition', 'criteria', 'num_findings', | ||
| 'investigation_id']] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| --- | ||
| short: DX Attack Graph | ||
| long: > | ||
| Severity-weighted, all-protocol pod-to-pod attack graph for one | ||
| dx-agent investigation. Renders attackgraph.Edge records emitted by | ||
| dx with weight (sum of CRS evidence severity) on the edges and | ||
| max_severity colouring the heat. v0 manual-load only — wires up to | ||
| the dx_attack_graph ClickHouse / Pixie ingest in v1. See README.md | ||
| in this directory. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| { | ||
| "variables": [ | ||
| { | ||
| "name": "start_time", | ||
| "type": "PX_STRING", | ||
| "description": "Start time of the window.", | ||
| "defaultValue": "-15m" | ||
| }, | ||
| { | ||
| "name": "clickhouse_dsn", | ||
| "type": "PX_STRING", | ||
| "description": "ClickHouse DSN: user:pass@host:port/db.", | ||
| "defaultValue": "forensic_analyst:changeme-analyst@clickhouse-forensic-soc-db.clickhouse.svc.cluster.local:9000/forensic_db" | ||
| }, | ||
| { | ||
| "name": "table", | ||
| "type": "PX_STRING", | ||
| "description": "dx_attack_graph_malicious (default; rule-ins only — benign is NOT pulled from ClickHouse) or dx_attack_graph (full table, includes benign).", | ||
| "defaultValue": "dx_attack_graph_malicious" | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ], | ||
| "globalFuncs": [ | ||
| { | ||
| "outputName": "dx_graph", | ||
| "func": { | ||
| "name": "dx_attack_graph", | ||
| "args": [ | ||
| {"name": "start_time", "variable": "start_time"}, | ||
| {"name": "clickhouse_dsn", "variable": "clickhouse_dsn"}, | ||
| {"name": "table", "variable": "table"} | ||
| ] | ||
| } | ||
| } | ||
| ], | ||
| "widgets": [ | ||
| { | ||
| "name": "DX Attack Graph", | ||
| "position": {"x": 0, "y": 0, "w": 12, "h": 5}, | ||
| "globalFuncOutputName": "dx_graph", | ||
| "displaySpec": { | ||
| "@type": "types.px.dev/px.vispb.Graph", | ||
| "adjacencyList": { | ||
| "fromColumn": "requestor", | ||
| "toColumn": "responder" | ||
| }, | ||
| "edgeWeightColumn": "weight", | ||
| "edgeColorColumn": "max_severity", | ||
| "edgeLabelColumn": "edge_kind", | ||
| "edgeThresholds": { | ||
| "mediumThreshold": 3, | ||
| "highThreshold": 4 | ||
| }, | ||
| "edgeHoverInfo": [ | ||
| "edge_kind", | ||
| "condition", | ||
| "criteria", | ||
| "weight", | ||
| "max_severity", | ||
| "confidence", | ||
| "num_findings", | ||
| "investigation_id" | ||
| ], | ||
| "edgeLength": 500 | ||
| } | ||
| }, | ||
| { | ||
| "name": "Edges", | ||
| "position": {"x": 0, "y": 5, "w": 12, "h": 4}, | ||
| "globalFuncOutputName": "dx_graph", | ||
| "displaySpec": { | ||
| "@type": "types.px.dev/px.vispb.Table" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.