Skip to content

Commit fd2753c

Browse files
committed
Add category grouping to egress allowlist
Categorizes egress destinations by caller binary (clair, oc-mirror, oc, podman, ansible, dnf, pip, other) based on OpenSnitch's PATH and CMDLINE fields. The YAML output is now grouped by category for easier review. Categories are derived from: - Binary basename (clairctl → clair, oc-mirror → oc-mirror, etc.) - For python callers: cmdline keywords (pip, dnf, ansible-galaxy, ansible-tmp) Updated test fixtures to include PATH/CMDLINE fields. Signed-off-by: Rafa Porres Molina <rporresm@redhat.com> Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 7c0a264 commit fd2753c

4 files changed

Lines changed: 96 additions & 45 deletions

File tree

docs/EGRESS_ALLOWLIST.yaml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Auto-generated by scripts/egress/analyze_egress.py
22
# Do not edit manually — updated by the egress-capture CI workflow
33
egress:
4-
connected:
5-
[]
6-
disconnected:
7-
[]
4+
connected: {}
5+
disconnected: {}

scripts/egress/analyze_egress.py

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,49 @@
5050

5151
# RFC5424 format patterns
5252
FIELD_PATTERNS_RFC5424 = {
53-
"dst": re.compile(r'\bDST="([^"]+)"'),
54-
"proto": re.compile(r'\bPROTO="(\w+)"'),
55-
"host": re.compile(r'\bDSTHOST="([^"]+)"'),
56-
"port": re.compile(r'\bDPT="(\d+)"'),
53+
"dst": re.compile(r'\bDST="([^"]+)"'),
54+
"proto": re.compile(r'\bPROTO="(\w+)"'),
55+
"host": re.compile(r'\bDSTHOST="([^"]+)"'),
56+
"port": re.compile(r'\bDPT="(\d+)"'),
57+
"path": re.compile(r'\bPATH="([^"]+)"'),
58+
"cmdline": re.compile(r'\bCMDLINE="([^"]*)"'),
5759
}
5860

5961

62+
def classify_caller(path: str, cmdline: str) -> str:
63+
"""Classify caller into a category based on binary path and command line."""
64+
import os
65+
basename = os.path.basename(path)
66+
67+
# Direct binary classifications
68+
if basename == "clairctl":
69+
return "clair"
70+
if basename == "oc-mirror" or "/oc-mirror" in path:
71+
return "oc-mirror"
72+
if basename == "oc":
73+
return "oc"
74+
if basename in ("podman", "skopeo"):
75+
return "podman"
76+
if basename == "openshift-install":
77+
return "openshift-install"
78+
79+
# Python calls — classify by cmdline keywords
80+
if basename.startswith("python"):
81+
if "pip" in cmdline or "pip3" in cmdline:
82+
return "pip"
83+
if "dnf" in cmdline:
84+
return "dnf"
85+
if "ansible-galaxy" in cmdline:
86+
return "ansible"
87+
if "ansible-tmp-" in cmdline or "/ansible/tmp/" in cmdline:
88+
return "ansible"
89+
# Generic python caller without specific markers
90+
return "other"
91+
92+
# Fallback
93+
return "other"
94+
95+
6096
def is_excluded(ip_str: str) -> bool:
6197
try:
6298
addr = ipaddress.ip_address(ip_str)
@@ -92,7 +128,7 @@ def parse_message(message: str) -> Optional[Dict]:
92128

93129

94130
def parse_message_rfc5424(message: str) -> Optional[Dict]:
95-
"""Parse RFC5424 syslog format: DST="ip" DSTHOST="host" DPT="port" PROTO="proto" """
131+
"""Parse RFC5424 syslog format: DST="ip" DSTHOST="host" DPT="port" PROTO="proto" PATH="..." CMDLINE="..." """
96132
fields = {}
97133
for name, pat in FIELD_PATTERNS_RFC5424.items():
98134
m = pat.search(message)
@@ -118,7 +154,12 @@ def parse_message_rfc5424(message: str) -> Optional[Dict]:
118154
except ValueError:
119155
return None
120156

121-
return {"dns": host, "protocol": proto, "port": port}
157+
# Classify caller from PATH and CMDLINE
158+
path = fields.get("path", "")
159+
cmdline = fields.get("cmdline", "")
160+
category = classify_caller(path, cmdline) if path else "other"
161+
162+
return {"dns": host, "protocol": proto, "port": port, "category": category}
122163

123164

124165
def parse_message_old(message: str) -> Optional[Dict]:
@@ -152,12 +193,13 @@ def parse_message_old(message: str) -> Optional[Dict]:
152193
except ValueError:
153194
return None
154195

155-
return {"dns": host, "protocol": proto, "port": port}
196+
# Old format doesn't have PATH/CMDLINE — category is unknown
197+
return {"dns": host, "protocol": proto, "port": port, "category": "other"}
156198

157199

158200
def parse_log_file(path: str) -> List[Dict]:
159201
"""Read a journald JSON or plain text log file and return deduplicated, sorted connection entries."""
160-
seen: Set[Tuple[str, str, int]] = set()
202+
seen: Set[Tuple[str, str, int, str]] = set()
161203
entries: List[Dict] = []
162204

163205
opener = gzip.open if path.endswith(".gz") else open
@@ -184,23 +226,36 @@ def parse_log_file(path: str) -> List[Dict]:
184226
if not entry:
185227
continue
186228

187-
key = (entry["dns"], entry["protocol"], entry["port"])
229+
key = (entry["dns"], entry["protocol"], entry["port"], entry["category"])
188230
if key not in seen:
189231
seen.add(key)
190232
entries.append(entry)
191233

192-
return sorted(entries, key=lambda e: (e["dns"], e["protocol"], e["port"]))
234+
return sorted(entries, key=lambda e: (e["category"], e["dns"], e["protocol"], e["port"]))
193235

194236

195237
def render_yaml(mode: str, entries: List[Dict]) -> str:
238+
"""Render entries as grouped YAML, sorted by category then dns."""
239+
from collections import defaultdict
240+
196241
lines = [f" {mode}:"]
197242
if not entries:
198-
lines.append(" []")
199-
else:
200-
for e in entries:
201-
lines.append(f" - dns: {e['dns']}")
202-
lines.append(f" protocol: {e['protocol']}")
203-
lines.append(f" port: {e['port']}")
243+
lines.append(" {}")
244+
return "\n".join(lines)
245+
246+
# Group by category
247+
by_category = defaultdict(list)
248+
for e in entries:
249+
by_category[e["category"]].append(e)
250+
251+
# Sort categories alphabetically
252+
for category in sorted(by_category.keys()):
253+
lines.append(f" {category}:")
254+
for e in by_category[category]:
255+
lines.append(f" - dns: {e['dns']}")
256+
lines.append(f" protocol: {e['protocol']}")
257+
lines.append(f" port: {e['port']}")
258+
204259
return "\n".join(lines)
205260

206261

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
connected:
2-
- dns: dns.google
3-
protocol: udp
4-
port: 53
5-
- dns: github.com
6-
protocol: tcp
7-
port: 443
8-
- dns: mirror.openshift.com
9-
protocol: tcp
10-
port: 443
11-
- dns: quay.io
12-
protocol: tcp
13-
port: 443
14-
- dns: registry.access.redhat.com
15-
protocol: tcp
16-
port: 443
2+
ansible:
3+
- dns: github.com
4+
protocol: tcp
5+
port: 443
6+
- dns: mirror.openshift.com
7+
protocol: tcp
8+
port: 443
9+
oc-mirror:
10+
- dns: registry.access.redhat.com
11+
protocol: tcp
12+
port: 443
13+
other:
14+
- dns: dns.google
15+
protocol: udp
16+
port: 53
17+
podman:
18+
- dns: quay.io
19+
protocol: tcp
20+
port: 443
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
{"__REALTIME_TIMESTAMP":"1745395815000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:51+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"38156\" DST=\"140.82.121.4\" DSTHOST=\"github.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6819\" UID=\"501\" PATH=\"/usr/bin/curl\" CMDLINE=\"curl -s https://github.com\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/curl,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
2-
{"__REALTIME_TIMESTAMP":"1745395816000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"42552\" DST=\"34.235.224.55\" DSTHOST=\"registry.access.redhat.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6821\" UID=\"501\" PATH=\"/usr/bin/curl\" CMDLINE=\"curl -s https://registry.access.redhat.com\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/curl,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
3-
{"__REALTIME_TIMESTAMP":"1745395817000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"60748\" DST=\"3.228.42.38\" DSTHOST=\"quay.io\" DPT=\"443\" PROTO=\"tcp\" PID=\"6823\" UID=\"501\" PATH=\"/usr/bin/curl\" CMDLINE=\"curl -s https://quay.io\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/curl,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
1+
{"__REALTIME_TIMESTAMP":"1745395815000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:51+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"38156\" DST=\"140.82.121.4\" DSTHOST=\"github.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6819\" UID=\"501\" PATH=\"/usr/bin/python3.12\" CMDLINE=\"/usr/bin/python3 /home/cloud-user/.ansible/tmp/ansible-tmp-1234.py\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/python3.12,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
2+
{"__REALTIME_TIMESTAMP":"1745395816000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"42552\" DST=\"34.235.224.55\" DSTHOST=\"registry.access.redhat.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6821\" UID=\"501\" PATH=\"/tmp/oc-mirror-123456/oc-mirror\" CMDLINE=\"/home/cloud-user/bin/oc-mirror --v2 --log-level info\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/tmp/oc-mirror-123456/oc-mirror,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
3+
{"__REALTIME_TIMESTAMP":"1745395817000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"60748\" DST=\"3.228.42.38\" DSTHOST=\"quay.io\" DPT=\"443\" PROTO=\"tcp\" PID=\"6823\" UID=\"501\" PATH=\"/usr/bin/podman\" CMDLINE=\"podman manifest inspect quay.io/openshift-release-dev/ocp-release\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/podman,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
44
{"__REALTIME_TIMESTAMP":"1745395818000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:51+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"58759\" DST=\"8.8.8.8\" DSTHOST=\"dns.google\" DPT=\"53\" PROTO=\"udp\" PID=\"6819\" UID=\"501\" PATH=\"/usr/bin/systemd-resolved\" CMDLINE=\"/usr/bin/systemd-resolved\" CWD=\"/\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/systemd-resolved,/usr/lib/systemd/systemd,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
5-
{"__REALTIME_TIMESTAMP":"1745395819000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"42553\" DST=\"151.101.1.194\" DSTHOST=\"registry.access.redhat.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6822\" UID=\"501\" PATH=\"/usr/bin/curl\" CMDLINE=\"curl\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/curl,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
6-
{"__REALTIME_TIMESTAMP":"1745395820000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"37654\" DST=\"192.168.5.100\" DSTHOST=\"api.enclave-test.example.com\" DPT=\"6443\" PROTO=\"tcp\" PID=\"7000\" UID=\"501\" PATH=\"/usr/bin/oc\" CMDLINE=\"oc\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/oc,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
7-
{"__REALTIME_TIMESTAMP":"1745395821000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"44321\" DST=\"10.0.0.1\" DSTHOST=\"internal.dns\" DPT=\"53\" PROTO=\"udp\" PID=\"999\" UID=\"995\" PATH=\"/usr/bin/systemd-resolved\" CMDLINE=\"/usr/bin/systemd-resolved\" CWD=\"/\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/systemd-resolved,/usr/lib/systemd/systemd,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
8-
{"__REALTIME_TIMESTAMP":"1745395822000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"55123\" DST=\"127.0.0.1\" DSTHOST=\"localhost\" DPT=\"8080\" PROTO=\"tcp\" PID=\"3000\" UID=\"501\" PATH=\"/usr/bin/python3\" CMDLINE=\"python3\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/python3,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
9-
{"__REALTIME_TIMESTAMP":"1745395823000000","SYSLOG_IDENTIFIER":"opensnitchd","MESSAGE":"opensnitchd started, version 1.8.0"}
10-
{"__REALTIME_TIMESTAMP":"1745395824000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"60749\" DST=\"44.195.245.145\" DSTHOST=\"quay.io\" DPT=\"443\" PROTO=\"tcp\" PID=\"5679\" UID=\"501\" PATH=\"/usr/bin/podman\" CMDLINE=\"podman\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/podman,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
11-
{"__REALTIME_TIMESTAMP":"1745395825000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"42123\" DST=\"23.23.23.23\" DSTHOST=\"mirror.openshift.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6000\" UID=\"501\" PATH=\"/usr/bin/curl\" CMDLINE=\"curl\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/curl,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}
5+
{"__REALTIME_TIMESTAMP":"1745395825000000","SYSLOG_IDENTIFIER":"opensnitch","MESSAGE":"<29>1 2026-04-24T17:00:52+02:00 6787 CONNECTION - [SRC=\"192.168.5.15\" SPT=\"42123\" DST=\"23.23.23.23\" DSTHOST=\"mirror.openshift.com\" DPT=\"443\" PROTO=\"tcp\" PID=\"6000\" UID=\"501\" PATH=\"/usr/bin/python3.12\" CMDLINE=\"/usr/bin/python3 /home/cloud-user/.ansible/tmp/ansible-tmp-5678.py\" CWD=\"/var/tmp\" CHECKSUMS=\"\" PROCTREE=\"/usr/bin/python3.12,/usr/bin/bash,\" ARG1=\"<nil>\" ARG2=\"<nil>\"]"}

0 commit comments

Comments
 (0)