Skip to content

Commit

Permalink
qubespolicy: add a tool to analyze policy in form of graph
Browse files Browse the repository at this point in the history
Output possible connections between VMs in form of dot file.

Fixes QubesOS/qubes-issues#2873
  • Loading branch information
marmarek committed Jun 27, 2017
1 parent 57882e7 commit 9ee7c16
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 1 deletion.
73 changes: 73 additions & 0 deletions doc/manpages/qrexec_policy_graph.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.. program:: qrexec-policy-graph

:program:`qrexec-policy-graph` -- Graph qrexec policy
=====================================================

Synopsis
--------

:command:`qrexec-policy-graph` skel-manpage.py [-h] [--include-ask] [--source *SOURCE* [*SOURCE* ...]] [--target *TARGET* [*TARGET* ...]] [--service *SERVICE* [*SERVICE* ...]] [--output *OUTPUT*] [--policy-dir POLICY_DIR] [--system-info SYSTEM_INFO]


Options
-------

.. option:: --help, -h

show this help message and exit

.. option:: --include-ask

Include `ask` action in graph. In most cases produce unreadable graphs
because many services contains `$anyvm $anyvm ask` rules. It's recommended to
limit graph using other options.

.. option:: --source

Limit graph to calls from *source*. You can specify multiple names.

.. option:: --target

Limit graph to calls to *target*. You can specify multiple names.

.. option:: --service

Limit graph to *service*. You can specify multiple names. This can be either
bare service name, or service with argument (joined with `+`). If bare
service name is given, output will contain also policies for specific
arguments.

.. option:: --output

Write to *output* instead of stdout. The file will be overwritten without
confirmation.

.. option:: --policy-dir

Look for policy in *policy-dir*. This can be useful to process policy
extracted from other system. This option adjust only base directory, if any
policy file contains `$include:path` with absolute path, it will try to load
the file from that location.
See also --system-info option.

.. option:: --system-info

Load system information from file instead of querying local qubesd instance.
The file should be in json format, as returned by `internal.GetSystemInfo`
qubesd method. This can be obtained by running in dom0:

qubesd-query -e -c /var/run/qubesd.internal.sock dom0 \
internal.GetSystemInfo dom0 | cut -b 3-

.. option:: --skip-labels

Do not include service names on the graph. Also, include only a single
connection between qubes if any service call is allowed there.


Authors
-------

| Marek Marczykowski-Górecki <marmarek at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80
2 changes: 1 addition & 1 deletion qubes-rpc-policy/generate-admin-policy
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import qubes.api.admin
parser = argparse.ArgumentParser(
description='Generate default Admin API policy')
parser.add_argument('--include-base', action='store',
default='/etc/qubes-rpc/policy/include',
default='include',
help='Base path for included paths (default: %(default)s)')
parser.add_argument('--destdir', action='store',
default='/etc/qubes-rpc/policy',
Expand Down
121 changes: 121 additions & 0 deletions qubespolicy/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 Marek Marczykowski-Górecki
# <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.

import argparse
import json
import os

import sys

import qubespolicy

parser = argparse.ArgumentParser(description='Graph qrexec policy')
parser.add_argument('--include-ask', action='store_true',
help='Include `ask` action in graph')
parser.add_argument('--source', action='store', nargs='+',
help='Limit graph to calls from *source*')
parser.add_argument('--target', action='store', nargs='+',
help='Limit graph to calls to *target*')
parser.add_argument('--service', action='store', nargs='+',
help='Limit graph to *service*')
parser.add_argument('--output', action='store',
help='Write to *output* instead of stdout')
parser.add_argument('--policy-dir', action='store',
default=qubespolicy.POLICY_DIR,
help='Look for policy in *policy-dir*')
parser.add_argument('--system-info', action='store',
help='Load system information from file instead of querying qubesd')
parser.add_argument('--skip-labels', action='store_true',
help='Do not include service names on the graph, also deduplicate '
'connections.')

def handle_single_action(args, action):
'''Get single policy action and output (or not) a line to add'''
if args.skip_labels:
service = ''
else:
service = action.service
if action.action == qubespolicy.Action.ask:
if args.include_ask:
# handle forced target=
if len(action.targets_for_ask) == 1:
return ' "{}" -> "{}" [label="{}" color=orange];\n'.format(
action.source, action.targets_for_ask[0], service)
else:
return ' "{}" -> "{}" [label="{}" color=orange];\n'.format(
action.source, action.original_target, service)
elif action.action == qubespolicy.Action.allow:
return ' "{}" -> "{}" [label="{}" color=red];\n'.format(
action.source, action.target, service)
return ''

def main(args=None):
args = parser.parse_args(args)

output = sys.stdout
if args.output:
output = open(args.output, 'w')

if args.system_info:
with open(args.system_info) as f_system_info:
system_info = json.load(f_system_info)
else:
system_info = qubespolicy.get_system_info()

sources = list(system_info['domains'].keys())
if args.source:
sources = args.source

targets = list(system_info['domains'].keys())
if args.target:
targets = args.target
else:
targets.append('$dispvm')
targets.extend('$dispvm:' + dom for dom in system_info['domains']
if system_info['domains'][dom]['dispvm_allowed'])

connections = set()

output.write('digraph g {\n')
for service in os.listdir(args.policy_dir):
if args.service and service not in args.service and \
not any(service.startswith(srv + '+') for srv in args.service):
continue

policy = qubespolicy.Policy(service, args.policy_dir)
for source in sources:
for target in targets:
try:
action = policy.evaluate(system_info, source, target)
line = handle_single_action(args, action)
if line in connections:
continue
if line:
output.write(line)
connections.add(line)
except qubespolicy.AccessDenied:
continue

output.write('}\n')
if args.output:
output.close()

if __name__ == '__main__':
sys.exit(main())
2 changes: 2 additions & 0 deletions rpm_spec/core-dom0.spec
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ fi
/usr/bin/qubesd*
/usr/bin/qrexec-policy
/usr/bin/qrexec-policy-agent
/usr/bin/qrexec-policy-graph

%{_mandir}/man1/qubes*.1*

Expand Down Expand Up @@ -372,6 +373,7 @@ fi
%{python3_sitelib}/qubespolicy/gtkhelpers.py
%{python3_sitelib}/qubespolicy/rpcconfirmation.py
%{python3_sitelib}/qubespolicy/utils.py
%{python3_sitelib}/qubespolicy/graph.py

%dir %{python3_sitelib}/qubespolicy/tests
%dir %{python3_sitelib}/qubespolicy/tests/__pycache__
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def get_console_scripts():
'console_scripts': list(get_console_scripts()) + [
'qrexec-policy = qubespolicy.cli:main',
'qrexec-policy-agent = qubespolicy.agent:main',
'qrexec-policy-graph = qubespolicy.graph:main',
],
'qubes.vm': [
'AppVM = qubes.vm.appvm:AppVM',
Expand Down

0 comments on commit 9ee7c16

Please sign in to comment.