diff --git a/qrexec/tests/tools/qrexec_legacy_convert.py b/qrexec/tests/tools/qrexec_legacy_convert.py index d9dcf95d..ae83ef56 100644 --- a/qrexec/tests/tools/qrexec_legacy_convert.py +++ b/qrexec/tests/tools/qrexec_legacy_convert.py @@ -31,6 +31,7 @@ def system_info(): system_info = { "dom0": { "icon": "black", + "label": "0xffffff", "template_for_dispvms": False, "guivm": None, "type": "AdminVM", @@ -39,6 +40,7 @@ def system_info(): }, "work": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -47,6 +49,7 @@ def system_info(): }, "personal": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -55,6 +58,7 @@ def system_info(): }, "sys-usb": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -63,6 +67,7 @@ def system_info(): }, "sys-usb-2": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -71,6 +76,7 @@ def system_info(): }, "dvm_template": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": True, "guivm": None, "type": "AppVM", diff --git a/qrexec/tests/tools/qrexec_policy_graph.py b/qrexec/tests/tools/qrexec_policy_graph.py index dff3722e..7f106b6d 100644 --- a/qrexec/tests/tools/qrexec_policy_graph.py +++ b/qrexec/tests/tools/qrexec_policy_graph.py @@ -31,6 +31,7 @@ def system_info(): system_info = { "dom0": { "icon": "black", + "label": "0xffffff", "template_for_dispvms": False, "guivm": None, "type": "AdminVM", @@ -39,6 +40,7 @@ def system_info(): }, "work": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -47,6 +49,7 @@ def system_info(): }, "personal": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -55,6 +58,7 @@ def system_info(): }, "sys-usb": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -63,6 +67,7 @@ def system_info(): }, "sys-usb-2": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": False, "guivm": None, "type": "AppVM", @@ -71,6 +76,7 @@ def system_info(): }, "dvm_template": { "icon": "red", + "label": "0xcc0000", "template_for_dispvms": True, "guivm": None, "type": "AppVM", @@ -104,6 +110,8 @@ def test_simple_graph(): main(["--policy-dir", policy_dir, "--output", output.name]) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service" color=red]; } """ @@ -126,6 +134,8 @@ def test_simple_ask(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service" color=orange]; } """ @@ -151,7 +161,10 @@ def test_simple_service(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service" color=red]; + "sys-usb" [color = "#cc0000", penwidth = 3]; "sys-usb" -> "personal" [label="test.Service" color=red]; } """ @@ -177,7 +190,10 @@ def test_simple_service_arg(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service" color=red]; + "sys-usb" [color = "#cc0000", penwidth = 3]; "sys-usb" -> "personal" [label="test.Service" color=red]; } """ @@ -202,6 +218,8 @@ def test_simple_service_arg_single(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service" color=red]; } """ @@ -227,6 +245,8 @@ def test_simple_service_no_wildcard(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service" color=red]; } """ @@ -253,6 +273,8 @@ def test_simple_service_no_wildcard_full(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "personal" [color = "#cc0000", penwidth = 3]; "work" -> "personal" [label="test.Service+arg allow" color=red]; "work" -> "personal" [label="test.Service+arg2 allow" color=red]; } @@ -277,6 +299,8 @@ def test_simple_redirect(): ) content = output.read().decode() expected = """digraph g { + "work" [color = "#cc0000", penwidth = 3]; + "dom0" [color = "#ffffff", penwidth = 3]; "work" -> "dom0" [label="test.Service" color=red]; } """ diff --git a/qrexec/tools/qrexec_policy_graph.py b/qrexec/tools/qrexec_policy_graph.py index e3e60ce3..5881fd89 100644 --- a/qrexec/tools/qrexec_policy_graph.py +++ b/qrexec/tools/qrexec_policy_graph.py @@ -78,8 +78,8 @@ ) -def handle_single_action(args, action): - """Get single policy action and output (or not) a line to add""" +def handle_single_action(args, action, system_info): + """Get single policy action and output (or not) lines to add""" if args.skip_labels: service = "" else: @@ -91,25 +91,49 @@ def handle_single_action(args, action): if action.rule.action.target: target = action.rule.action.target if args.target and target not in args.target: - return "" + return [] + + lines = [] + + # create nodes for source and target, with colors + for node in [action.request.source, target]: + if node.startswith("@"): # non-literal qubes + node_attributes = "style = dotted" + else: + node_color = system_info["domains"][node]["label"].replace( + "0x", "#" + ) + node_attributes = f'color = "{node_color}", penwidth = 3' + lines.append(f' "{node}" [{node_attributes}];\n') + + # create edges with services as labels if args.full_output: color = "orange" if isinstance(action, parser.AskResolution) else "red" - return ( + lines.append( f' "{action.request.source}" -> "{target}" ' f'[label="{service} {action.rule.action}" color={color}];\n' ) + return lines + if isinstance(action, parser.AskResolution): if args.include_ask: - return ( - f' "{action.request.source}" -> "{target}" ' - f'[label="{service}" color=orange];\n' + lines.append( + ( + f' "{action.request.source}" -> "{target}" ' + f'[label="{service}" color=orange];\n' + ) ) + return lines elif isinstance(action, parser.AllowResolution): - return ( - f' "{action.request.source}" -> "{target}" ' - f'[label="{service}" color=red];\n' + lines.append( + ( + f' "{action.request.source}" -> "{target}" ' + f'[label="{service}" color=red];\n' + ) ) - return "" + return lines + + return [] def main(args=None, output=sys.stdout): @@ -201,14 +225,15 @@ def main(args=None, output=sys.stdout): system_info=system_info, ) action = policy.evaluate(request) - line = handle_single_action(args, action) + except exc.AccessDenied: + continue + + for line in handle_single_action(args, action, system_info): if line in connections: continue if line: output.write(line) connections.add(line) - except exc.AccessDenied: - continue output.write("}\n") if args.output: diff --git a/qrexec/utils.py b/qrexec/utils.py index dc4d0af4..29c6f8e3 100644 --- a/qrexec/utils.py +++ b/qrexec/utils.py @@ -127,6 +127,7 @@ class SystemInfoEntry(TypedDict): default_dispvm: Optional[str] power_state: str icon: str + label: str guivm: Optional[str] uuid: Optional[str] name: str