Skip to content

Commit ae17477

Browse files
committed
feat(openshift): Address security concern by creating temporary dir
- Update `collect_ovn_data.py` and `analyze_placement.py` to require a private temporary directory for file operations, improving security. - Implement safe file writing and appending methods in `ovn_utils.py` to prevent symlink attacks. - Refactor file handling to ensure all output is directed to the specified temporary directory. Signed-off-by: Matteo Dallaglio <[email protected]>
1 parent f16698f commit ae17477

File tree

5 files changed

+212
-88
lines changed

5 files changed

+212
-88
lines changed

docs/data.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,14 +581,14 @@
581581
"name": "visualize-ovn-topology",
582582
"description": "Generate and visualize OVN-Kubernetes network topology diagram",
583583
"synopsis": "/openshift:visualize-ovn-topology",
584-
"argument_hint": "\"\""
584+
"argument_hint": ""
585585
}
586586
],
587587
"skills": [
588588
{
589589
"name": "generating-ovn-topology",
590590
"id": "generating-ovn-topology",
591-
"description": "Generates and displays OVN-Kubernetes network topology diagrams showing logical switches, routers, ports, and load balancers with IP/MAC addresses in Mermaid format"
591+
"description": "Generates and displays OVN-Kubernetes network topology diagrams showing logical switches, routers, ports with IP/MAC addresses in Mermaid format"
592592
}
593593
],
594594
"hooks": [],

plugins/openshift/skills/generating-ovn-topology/SKILL.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: generating-ovn-topology
3-
description: Generates and displays OVN-Kubernetes network topology diagrams showing logical switches, routers, ports, and load balancers with IP/MAC addresses in Mermaid format
3+
description: Generates and displays OVN-Kubernetes network topology diagrams showing logical switches, routers, ports with IP/MAC addresses in Mermaid format
44
tools: [Bash, Read, Write]
55
---
66

@@ -91,9 +91,15 @@ tools: [Bash, Read, Write]
9191
3. **Check Output File**: Ask user if `ovn-topology-diagram.md` exists:
9292
- (1) Overwrite, (2) Custom path, (3) Timestamp, (4) Cancel
9393

94-
4. **Collect OVN Data**: Get full topology data from the cluster
94+
4. **Create Private Temp Directory**: Create a private temporary directory using `mkdtemp` and use it for all temporary files.
9595

96-
Run: `scripts/collect_ovn_data.py "$KC"`
96+
```bash
97+
TMPDIR=$(mktemp -d)
98+
```
99+
100+
5. **Collect OVN Data**: Get full topology data from the cluster
101+
102+
Run: `scripts/collect_ovn_data.py "$KC" "$TMPDIR"`
97103

98104
Detail files written to `$TMPDIR`:
99105
- `ovn_switches_detail.txt` - node|uuid|name|other_config
@@ -102,23 +108,23 @@ tools: [Bash, Read, Write]
102108
- `ovn_lrps_detail.txt` - node|name|mac|networks|options
103109
- `ovn_pods_detail.txt` - namespace|name|ip|node
104110

105-
5. **Analyze Placement**: Determine per-node vs cluster-wide components
111+
6. **Analyze Placement**: Determine per-node vs cluster-wide components
106112

107113
Run: `scripts/analyze_placement.py "$TMPDIR"`
108114

109115
Placement results written to `$TMPDIR`:
110116
- `ovn_switch_placement.txt` - name|placement (per-node|cluster-wide|cluster-wide-visual)
111117
- `ovn_router_placement.txt` - name|placement (per-node|cluster-wide|cluster-wide-visual)
112118

113-
6. **Generate Diagram**: Create Mermaid `graph BT` diagram
119+
7. **Generate Diagram**: Create Mermaid `graph BT` diagram
114120
- Read `$TMPDIR/ovn_switch_placement.txt` to determine where each switch goes
115121
- Read `$TMPDIR/ovn_router_placement.txt` to determine where each router goes
116122
- Read detail files directly (ovn_switches_detail.txt, ovn_routers_detail.txt, etc.)
117123
- Skip UUID column when parsing switches/routers detail files
118124
- If placement is `per-node` → put inside node subgraph
119125
- If placement is `cluster-wide` or `cluster-wide-visual` → put outside subgraphs
120126

121-
7. **Save & Report**: Write diagram to file, show summary, clean up temporary files
127+
8. **Save & Report**: Write diagram to file, show summary, clean up temporary files
122128

123129
**CRITICAL RULES**:
124130
- ❌ NO codebase searching for IPs/MACs
@@ -127,8 +133,9 @@ tools: [Bash, Read, Write]
127133
- ❌ NO direct kubectl commands (must use helper scripts only)
128134
- ✅ Always use `KUBECONFIG="$KC" kubectl --kubeconfig="$KC"` for kubectl commands
129135
- ✅ Use helper scripts for architecture discovery
130-
- ✅ Temporary files use `$TMPDIR` (defaults to `/tmp/` if not set)
131-
- ✅ Clean up temporary files when done
136+
-**SECURITY**: Create private temp directory with `TMPDIR=$(mktemp -d)` - never use `/tmp` directly
137+
- ✅ Temporary files use `$TMPDIR` (private directory created with mkdtemp)
138+
- ✅ Clean up temporary files when done: `rm -rf "$TMPDIR"`
132139

133140
## Safety & Security Guarantees
134141

@@ -177,7 +184,7 @@ All helper scripts are in the `scripts/` directory.
177184
|--------|---------|-------|--------|
178185
| [detect-cluster.sh](scripts/detect-cluster.sh) | Find OVN cluster kubeconfig across all contexts. Scans multiple kubeconfig files and all their contexts. Returns parseable list. | None | Parseable list to stdout: `index|kubeconfig|cluster|nodes|namespace`. Exit: 0=success, 1=none found |
179186
| [check_permissions.py](scripts/check_permissions.py) | Check user permissions and warn if write access detected. | KUBECONFIG path | Exit: 0=proceed, 1=cancelled/error, 2=write perms (needs user confirmation) |
180-
| [collect_ovn_data.py](scripts/collect_ovn_data.py) | **Data collector**: Queries each node for all data, with **graceful degradation** (continues on node failures). Writes detail files. | KUBECONFIG path | Detail files: `ovn_switches_detail.txt`, `ovn_routers_detail.txt`, `ovn_lsps_detail.txt`, `ovn_lrps_detail.txt`, `ovn_pods_detail.txt` |
187+
| [collect_ovn_data.py](scripts/collect_ovn_data.py) | **Data collector**: Queries each node for all data, with **graceful degradation** (continues on node failures). Writes detail files. | KUBECONFIG path, TMPDIR | Detail files: `ovn_switches_detail.txt`, `ovn_routers_detail.txt`, `ovn_lsps_detail.txt`, `ovn_lrps_detail.txt`, `ovn_pods_detail.txt` |
181188
| [analyze_placement.py](scripts/analyze_placement.py) | **Placement analyzer**: Analyzes UUID patterns from detail files to determine per-node vs cluster-wide placement. | TMPDIR (reads detail files) | Placement files: `ovn_switch_placement.txt`, `ovn_router_placement.txt` |
182189

183190
---
@@ -254,7 +261,7 @@ graph BT
254261

255262
1. **Graph Direction**: Always `graph BT` (bottom-to-top)
256263

257-
2. **Component Placement**: Determined by `/tmp/ovn_*_placement.txt`
264+
2. **Component Placement**: Determined by `/$TMPDIR/ovn_*_placement.txt`
258265
- `per-node` → INSIDE node subgraph
259266
- `cluster-wide` or `cluster-wide-visual` → OUTSIDE all subgraphs, **defined AFTER all node subgraphs**
260267
- **CRITICAL**: Define ALL cluster-wide components AFTER all nodes to position them at the TOP
@@ -390,10 +397,9 @@ MGMT_cp --> LSP_mgmt --> LS_node
390397
1. Generate complete Mermaid diagram following structure above
391398
2. Save to file chosen by user
392399
3. Show summary: nodes, switches, routers, ports, LBs, mode
393-
4. Clean up temporary files:
400+
4. Clean up temporary directory:
394401
```bash
395-
rm -f "$TMPDIR/ovn_switch_placement.txt" "$TMPDIR/ovn_router_placement.txt"
396-
rm -f "$TMPDIR/ovn_switches_detail.txt" "$TMPDIR/ovn_routers_detail.txt"
397-
rm -f "$TMPDIR/ovn_pods_detail.txt" "$TMPDIR/ovn_lsps_detail.txt" "$TMPDIR/ovn_lrps_detail.txt"
402+
rm -rf "$TMPDIR"
398403
```
404+
399405
5. Tell user to open file in IDE to view rendered diagram

plugins/openshift/skills/generating-ovn-topology/scripts/analyze_placement.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
"""
33
analyze_placement.py - Analyze OVN component placement from collected data
44
5-
Usage: analyze_placement.py [TMPDIR]
5+
Usage: analyze_placement.py TMPDIR
66
77
This script analyzes UUID patterns from previously collected OVN data to determine
88
component placement (per-node vs cluster-wide).
99
10-
Input files (read from $TMPDIR, defaults to /tmp):
10+
Input files (read from TMPDIR):
1111
- ovn_switches_detail.txt - node|uuid|name|other_config
1212
- ovn_routers_detail.txt - node|uuid|name|external_ids|options
1313
14-
Output files (written to $TMPDIR):
14+
Output files (written to TMPDIR):
1515
- ovn_switch_placement.txt - name|placement (per-node|cluster-wide|cluster-wide-visual)
1616
- ovn_router_placement.txt - name|placement (per-node|cluster-wide|cluster-wide-visual)
1717
@@ -27,6 +27,9 @@
2727
from collections import defaultdict
2828
from typing import Dict, List, Set, Tuple
2929

30+
# Import shared utilities (must be in same directory or in PYTHONPATH)
31+
from ovn_utils import safe_write_file
32+
3033

3134
class PlacementAnalyzer:
3235
"""Analyze OVN component placement from collected data."""
@@ -46,8 +49,12 @@ class PlacementAnalyzer:
4649
PLACEMENT_CLUSTER_WIDE = "cluster-wide"
4750
PLACEMENT_CLUSTER_WIDE_VISUAL = "cluster-wide-visual"
4851

49-
def __init__(self, tmpdir: str = "/tmp"):
50-
self.tmpdir = tmpdir
52+
def __init__(self, tmpdir: str):
53+
"""Initialize PlacementAnalyzer with a private temporary directory.
54+
55+
Args:
56+
tmpdir: Private temporary directory path.
57+
"""
5158

5259
# Input files (with UUIDs)
5360
self.switches_file = os.path.join(tmpdir, self.SWITCHES_DETAIL_FILE)
@@ -166,6 +173,7 @@ def _analyze_component_placement(
166173

167174
return placement
168175

176+
169177
def analyze_uuid_patterns(self):
170178
"""Analyze UUID patterns to determine per-node vs cluster-wide placement."""
171179
print()
@@ -207,14 +215,18 @@ def analyze_uuid_patterns(self):
207215
placement = switch_placement[self.JOIN_SWITCH_NAME]
208216
print(f" → join: {placement.upper()} (detected) → keeping as-is")
209217

210-
# Write placement files
211-
with open(self.switch_placement_file, 'w') as f:
212-
for name in sorted(switch_placement.keys()):
213-
f.write(f"{name}|{switch_placement[name]}\n")
218+
# Write placement files using safe write to prevent symlink attacks
219+
switch_content = "".join(
220+
f"{name}|{switch_placement[name]}\n"
221+
for name in sorted(switch_placement.keys())
222+
)
223+
safe_write_file(self.switch_placement_file, switch_content)
214224

215-
with open(self.router_placement_file, 'w') as f:
216-
for name in sorted(router_placement.keys()):
217-
f.write(f"{name}|{router_placement[name]}\n")
225+
router_content = "".join(
226+
f"{name}|{router_placement[name]}\n"
227+
for name in sorted(router_placement.keys())
228+
)
229+
safe_write_file(self.router_placement_file, router_content)
218230

219231
print()
220232
print("✓ Placement analysis complete:")
@@ -289,7 +301,14 @@ def run(self) -> int:
289301

290302
def main():
291303
"""Main entry point."""
292-
tmpdir = sys.argv[1] if len(sys.argv) > 1 else os.environ.get('TMPDIR', '/tmp')
304+
if len(sys.argv) < 2:
305+
print(
306+
"Usage: analyze_placement.py TMPDIR",
307+
file=sys.stderr,
308+
)
309+
return 1
310+
311+
tmpdir = sys.argv[1]
293312

294313
analyzer = PlacementAnalyzer(tmpdir)
295314
return analyzer.run()

0 commit comments

Comments
 (0)