From 3d803acfdea2eb2218c4f582bb6e226ef4f1505c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 27 Jun 2017 01:55:17 +0200 Subject: [PATCH] Generate policy for Admin API calls based on annotations on actual methods This ease Admin API administration, and also adds checking if qrexec policy + scripts matches actual Admin API methods implementation. The idea is to classify every Admin API method as either local read-only, local read-write, global read-only or global read-write. Where local/global means affecting a single VM, or the whole system. See QubesOS/qubes-issues#2871 for details. Fixes QubesOS/qubes-issues#2871 --- Makefile | 28 ++++-- qubes-rpc-policy/admin-default | 13 --- qubes-rpc-policy/admin-global-ro | 13 +++ qubes-rpc-policy/admin-global-rwx | 11 +++ qubes-rpc-policy/admin-local-ro | 14 +++ .../{admin-all => admin-local-rwx} | 6 +- qubes-rpc-policy/generate-admin-policy | 95 +++++++++++++++++++ rpm_spec/core-dom0.spec | 5 +- 8 files changed, 158 insertions(+), 27 deletions(-) delete mode 100644 qubes-rpc-policy/admin-default create mode 100644 qubes-rpc-policy/admin-global-ro create mode 100644 qubes-rpc-policy/admin-global-rwx create mode 100644 qubes-rpc-policy/admin-local-ro rename qubes-rpc-policy/{admin-all => admin-local-rwx} (62%) create mode 100755 qubes-rpc-policy/generate-admin-policy diff --git a/Makefile b/Makefile index 64492ec4..675d8866 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,6 @@ OS ?= Linux PYTHON ?= python3 ADMIN_API_METHODS_SIMPLE = \ - admin.vm.List \ admin.vmclass.List \ admin.Events \ admin.backup.Execute \ @@ -17,6 +16,7 @@ ADMIN_API_METHODS_SIMPLE = \ admin.label.Create \ admin.label.Get \ admin.label.List \ + admin.label.Index \ admin.label.Remove \ admin.pool.Add \ admin.pool.Info \ @@ -84,6 +84,7 @@ ADMIN_API_METHODS_SIMPLE = \ admin.vm.tag.List \ admin.vm.tag.Remove \ admin.vm.tag.Set \ + admin.vm.volume.Clone \ admin.vm.volume.Info \ admin.vm.volume.List \ admin.vm.volume.ListSnapshots \ @@ -91,10 +92,6 @@ ADMIN_API_METHODS_SIMPLE = \ admin.vm.volume.Revert \ $(null) -ADMIN_API_METHODS := $(ADMIN_API_METHODS_SIMPLE) \ - admin.vm.volume.Import \ - $(null) - ifeq ($(OS),Linux) DATADIR ?= /var/lib/qubes STATEDIR ?= /var/run/qubes @@ -173,15 +170,26 @@ endif install qubes-rpc/qubesd-query-fast $(DESTDIR)/usr/libexec/qubes/ for method in $(ADMIN_API_METHODS_SIMPLE); do \ ln -s ../../usr/libexec/qubes/qubesd-query-fast \ - $(DESTDIR)/etc/qubes-rpc/$$method; \ + $(DESTDIR)/etc/qubes-rpc/$$method || exit 1; \ done install qubes-rpc/admin.vm.volume.Import $(DESTDIR)/etc/qubes-rpc/ - for method in $(ADMIN_API_METHODS); do \ - install -m 0644 qubes-rpc-policy/admin-default \ - $(DESTDIR)/etc/qubes-rpc/policy/$$method; \ + PYTHONPATH=.:test-packages qubes-rpc-policy/generate-admin-policy \ + --destdir=$(DESTDIR)/etc/qubes-rpc/policy \ + --exclude admin.vm.Create.AdminVM \ + admin.vm.CreateInPool.AdminVM \ + admin.vm.device.testclass.Attach \ + admin.vm.device.testclass.Detach \ + admin.vm.device.testclass.List \ + admin.vm.device.testclass.Available + # sanity check + for method in $(DESTDIR)/etc/qubes-rpc/policy/admin.*; do \ + ls $(DESTDIR)/etc/qubes-rpc/$$(basename $$method) >/dev/null || exit 1; \ done install -d $(DESTDIR)/etc/qubes-rpc/policy/include - install -m 0644 qubes-rpc-policy/admin-all \ + install -m 0644 qubes-rpc-policy/admin-local-ro \ + qubes-rpc-policy/admin-local-rwx \ + qubes-rpc-policy/admin-global-ro \ + qubes-rpc-policy/admin-global-rwx \ $(DESTDIR)/etc/qubes-rpc/policy/include/ mkdir -p "$(DESTDIR)$(FILESDIR)" diff --git a/qubes-rpc-policy/admin-default b/qubes-rpc-policy/admin-default deleted file mode 100644 index fc4ed4a7..00000000 --- a/qubes-rpc-policy/admin-default +++ /dev/null @@ -1,13 +0,0 @@ -## Note that policy parsing stops at the first match, -## so adding anything below "$anyvm $anyvm action" line will have no effect - -## Please use a single # to start your custom comments - -## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions - -## Include a single file for all admin.* methods to ease setting up Management VM. -## To allow only specific actions, edit specific policy file, like this one. To -## allow all of them, edit /etc/qubes-rpc/include/admin-all. -$include:/etc/qubes-rpc/policy/include/admin-all - -$anyvm $anyvm deny diff --git a/qubes-rpc-policy/admin-global-ro b/qubes-rpc-policy/admin-global-ro new file mode 100644 index 00000000..48cf561a --- /dev/null +++ b/qubes-rpc-policy/admin-global-ro @@ -0,0 +1,13 @@ +## This file is included from all global read-only admin.* policy files +## _in default configuration_. To allow only specific action, +## edit specific policy file. + +## Note that policy parsing stops at the first match, + +## Please use a single # to start your custom comments + +## Include all already having write access +$include:include/admin-global-rwx + +## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions + diff --git a/qubes-rpc-policy/admin-global-rwx b/qubes-rpc-policy/admin-global-rwx new file mode 100644 index 00000000..02c0a8d2 --- /dev/null +++ b/qubes-rpc-policy/admin-global-rwx @@ -0,0 +1,11 @@ +## This file is included from all global read-write admin.* policy files +## _in default configuration_. To allow only specific action, +## edit specific policy file. + +## Note that policy parsing stops at the first match, +## so adding anything below "$anyvm $anyvm action" line will have no effect + +## Please use a single # to start your custom comments + +## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions + diff --git a/qubes-rpc-policy/admin-local-ro b/qubes-rpc-policy/admin-local-ro new file mode 100644 index 00000000..b16e9f5e --- /dev/null +++ b/qubes-rpc-policy/admin-local-ro @@ -0,0 +1,14 @@ +## This file is included from all local read-only admin.* policy files +## _in default configuration_. To allow only specific action, +## edit specific policy file. + +## Note that policy parsing stops at the first match, +## so adding anything below "$anyvm $anyvm action" line will have no effect + +## Please use a single # to start your custom comments + +## Include all already having write access +$include:include/admin-local-rwx + +## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions + diff --git a/qubes-rpc-policy/admin-all b/qubes-rpc-policy/admin-local-rwx similarity index 62% rename from qubes-rpc-policy/admin-all rename to qubes-rpc-policy/admin-local-rwx index 33e391ba..8c827af3 100644 --- a/qubes-rpc-policy/admin-all +++ b/qubes-rpc-policy/admin-local-rwx @@ -1,5 +1,6 @@ -## This file is included from all admin.* policy files _in default -## configuration_. To allow only specific action, edit specific policy file. +## This file is included from all local read-write admin.* policy files +## _in default configuration_. To allow only specific action, +## edit specific policy file. ## Note that policy parsing stops at the first match, ## so adding anything below "$anyvm $anyvm action" line will have no effect @@ -8,4 +9,3 @@ ## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions -$anyvm $anyvm deny diff --git a/qubes-rpc-policy/generate-admin-policy b/qubes-rpc-policy/generate-admin-policy new file mode 100755 index 00000000..fb00ba3d --- /dev/null +++ b/qubes-rpc-policy/generate-admin-policy @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +# coding=utf-8 +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import argparse +import os + +import sys + +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', + help='Base path for included paths (default: %(default)s)') +parser.add_argument('--destdir', action='store', + default='/etc/qubes-rpc/policy', + help='Directory where write output files to (default: %(default)s)') +parser.add_argument('--verbose', action='store_true', default=False, + help='Be verbose') +parser.add_argument('--exclude', action='store', nargs='*', + help='Exclude service') +parser.add_argument('service', nargs='*', action='store', + help='Generate policy for those services (default: all)') + +default_policy_header = '''\ +## Note that policy parsing stops at the first match. + +## Please use a single # to start your custom comments + +## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions + +## Include a common file for all admin.* methods to ease setting up +## Management VM. +## To allow only specific actions, edit specific policy file, like this one. To +## allow all of them, edit appropriate /etc/qubes-rpc/include/admin-*. + +''' + +def write_default_policy(args, apiname, clasifiers): + ''' Write single default policy for given API call ''' + assert 'scope' in clasifiers, \ + 'Method {} lack scope classifier'.format(apiname) + assert any(attr in clasifiers for attr in ('read', 'write', 'execute')), \ + 'Method {} lack read/write/execute classifier'.format(apiname) + assert clasifiers['scope'] in ('local', 'global'), \ + 'Method {} have invalid scope: {}'.format(apiname, clasifiers['scope']) + + file_to_include = 'admin-{scope}-{rwx}'.format( + scope=clasifiers['scope'], + rwx=('rwx' if clasifiers.get('write', False) or + clasifiers.get('execute', False) + else 'ro')) + + if args.verbose: + print('Service {}: include {}'.format(apiname, file_to_include), + file=sys.stderr) + with open(os.path.join(args.destdir, apiname), 'w') as f: + f.write(default_policy_header) + f.write('$include:{}\n'.format( + os.path.join(args.include_base, file_to_include))) + + +def main(args=None): + ''' Main function of default-admin-policy tool''' + args = parser.parse_args(args) + + for func, apiname, _ in qubes.api.admin.QubesAdminAPI.list_methods(): + if args.service and apiname not in args.service: + continue + if args.exclude and apiname in args.exclude: + continue + write_default_policy(args, apiname, func.classifiers) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index ff825337..abebb345 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -410,7 +410,10 @@ fi /etc/xen/scripts/block-origin /etc/xen/scripts/vif-route-qubes %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/admin.* -%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-all +%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-local-ro +%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-local-rwx +%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-global-ro +%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-global-rwx %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.FeaturesRequest %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.Filecopy %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.GetImageRGBA