Pārlūkot izejas kodu

qubespolicy: ask to create default policy if none is found

Fixes QubesOS/qubes-issues#3005
Marek Marczykowski-Górecki 6 gadi atpakaļ
vecāks
revīzija
12661dccf8

+ 8 - 0
qubespolicy/__init__.py

@@ -47,6 +47,12 @@ class PolicySyntaxError(AccessDenied):
         super(PolicySyntaxError, self).__init__(
             '{}:{}: {}'.format(filename, lineno, msg))
 
+class PolicyNotFound(AccessDenied):
+    ''' Policy was not found for this service '''
+    def __init__(self, service_name):
+        super(PolicyNotFound, self).__init__(
+            'Policy not found for service {}'.format(service_name))
+
 
 class Action(enum.Enum):
     ''' Action as defined by policy '''
@@ -486,6 +492,8 @@ class Policy(object):
         if not os.path.exists(policy_file):
             # fallback to policy without specific argument set (if any)
             policy_file = os.path.join(policy_dir, service.split('+')[0])
+        if not os.path.exists(policy_file):
+            raise PolicyNotFound(service)
 
         #: policy storage directory
         self.policy_dir = policy_dir

+ 13 - 0
qubespolicy/agent.py

@@ -30,6 +30,7 @@ from gi.repository import GLib
 # pylint: enable=import-error
 
 import qubespolicy.rpcconfirmation
+import qubespolicy.policycreateconfirmation
 # pylint: enable=wrong-import-position
 
 class PolicyAgent(object):
@@ -45,6 +46,11 @@ class PolicyAgent(object):
           <arg type='a{ss}' name='icons' direction='in'/>
           <arg type='s' name='response' direction='out'/>
         </method>
+        <method name='ConfirmPolicyCreate'>
+          <arg type='s' name='source' direction='in'/>
+          <arg type='s' name='service_name' direction='in'/>
+          <arg type='b' name='response' direction='out'/>
+        </method>
       </interface>
     </node>
     """
@@ -63,6 +69,13 @@ class PolicyAgent(object):
             targets, default_target or None)
         return response or ''
 
+    @staticmethod
+    def ConfirmPolicyCreate(source, service_name):
+        # pylint: disable=invalid-name
+
+        response = qubespolicy.policycreateconfirmation.confirm(
+            source, service_name)
+        return response
 
 def main():
     loop = GLib.MainLoop()

+ 31 - 1
qubespolicy/cli.py

@@ -20,6 +20,7 @@
 import argparse
 import logging
 import logging.handlers
+import os
 
 import sys
 
@@ -46,6 +47,20 @@ parser.add_argument('process_ident', metavar='process-ident',
     help='Qrexec process identifier - for connecting data channel')
 
 
+def create_default_policy(service_name):
+    policy_file = os.path.join(qubespolicy.POLICY_DIR, service_name)
+    with open(policy_file, "w") as policy:
+        policy.write(
+            "## Policy file automatically created on first service call.\n")
+        policy.write(
+            "## Fill free to edit.\n")
+        policy.write("## Note that policy parsing stops at the first match\n")
+        policy.write("\n")
+        policy.write("## Please use a single # to start your custom comments\n")
+        policy.write("\n")
+        policy.write("$anyvm  $anyvm  ask\n")
+
+
 def main(args=None):
     args = parser.parse_args(args)
 
@@ -64,7 +79,22 @@ def main(args=None):
         log.error(log_prefix + 'error getting system info: ' + str(e))
         return 1
     try:
-        policy = qubespolicy.Policy(args.service_name)
+        try:
+            policy = qubespolicy.Policy(args.service_name)
+        except qubespolicy.PolicyNotFound:
+            service_name = args.service_name.split('+')[0]
+            import pydbus
+            bus = pydbus.SystemBus()
+            proxy = bus.get('org.qubesos.PolicyAgent',
+                '/org/qubesos/PolicyAgent')
+            create_policy = proxy.ConfirmPolicyCreate(
+                args.domain, service_name)
+            if create_policy:
+                create_default_policy(service_name)
+                policy = qubespolicy.Policy(args.service_name)
+            else:
+                raise
+
         action = policy.evaluate(system_info, args.domain, args.target)
         if args.assume_yes_for_ask and action.action == qubespolicy.Action.ask:
             action.action = qubespolicy.Action.allow

+ 141 - 0
qubespolicy/glade/PolicyCreateConfirmationWindow.glade

@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkMessageDialog" id="PolicyCreateConfirmationWindow">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Default service policy</property>
+    <property name="icon_name">dialog-warning</property>
+    <property name="type_hint">dialog</property>
+    <property name="message_type">question</property>
+    <property name="buttons">ok-cancel</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">2</property>
+            <child>
+              <object class="GtkLabel" id="messageLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Policy for requested service does not exist.
+Do you want to create default one (ask for everything)?</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="padding">2</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="row_spacing">2</property>
+                <property name="column_spacing">2</property>
+                <child>
+                  <object class="GtkLabel" id="sourceLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_left">4</property>
+                    <property name="label" translatable="yes">Source:</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="serviceLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_left">4</property>
+                    <property name="label" translatable="yes">Service:</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="sourceEntry">
+                    <property name="visible">True</property>
+                    <property name="sensitive">False</property>
+                    <property name="can_focus">True</property>
+                    <property name="editable">False</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="serviceEntry">
+                    <property name="visible">True</property>
+                    <property name="sensitive">False</property>
+                    <property name="can_focus">True</property>
+                    <property name="editable">False</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="confirmLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Type capital YES to confirm: </property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="confirmEntry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="activates_default">True</property>
+                    <property name="caps_lock_warning">False</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>

+ 82 - 0
qubespolicy/policycreateconfirmation.py

@@ -0,0 +1,82 @@
+# -*- encoding: utf-8 -*-
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2017 Marek Marczykowski-Górecki
+#                               <marmarek@invisiblethingslab.com>
+#
+# 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 os
+
+import pkg_resources
+
+# pylint: disable=import-error,wrong-import-position
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+# pylint: enable=import-error
+
+class PolicyCreateConfirmationWindow(object):
+    # pylint: disable=too-few-public-methods
+    _source_file = pkg_resources.resource_filename('qubespolicy',
+        os.path.join('glade', "PolicyCreateConfirmationWindow.glade"))
+    _source_id = {'window': "PolicyCreateConfirmationWindow",
+                  'ok': "okButton",
+                  'cancel': "cancelButton",
+                  'source': "sourceEntry",
+                  'service': "serviceEntry",
+                  'confirm': "confirmEntry",
+                  }
+
+    def __init__(self, source, service):
+        self._gtk_builder = Gtk.Builder()
+        self._gtk_builder.add_from_file(self._source_file)
+        self._window = self._gtk_builder.get_object(
+            self._source_id['window'])
+        self._rpc_ok_button = self._gtk_builder.get_object(
+            self._source_id['ok'])
+        self._rpc_cancel_button = self._gtk_builder.get_object(
+            self._source_id['cancel'])
+        self._service_entry = self._gtk_builder.get_object(
+            self._source_id['service'])
+        self._source_entry = self._gtk_builder.get_object(
+            self._source_id['source'])
+        self._confirm_entry = self._gtk_builder.get_object(
+            self._source_id['confirm'])
+
+        self._source_entry.set_text(source)
+        self._service_entry.set_text(service)
+
+        # make OK button the default
+        ok_button = self._window.get_widget_for_response(Gtk.ResponseType.OK)
+        ok_button.set_can_default(True)
+        ok_button.grab_default()
+
+    def run(self):
+        self._window.set_keep_above(True)
+        self._window.connect("delete-event", Gtk.main_quit)
+        self._window.show_all()
+
+        response = self._window.run()
+
+        self._window.hide()
+        if response == Gtk.ResponseType.OK:
+            return self._confirm_entry.get_text() == 'YES'
+        return False
+
+def confirm(source, service):
+    window = PolicyCreateConfirmationWindow(source, service)
+
+    return window.run()

+ 3 - 0
rpm_spec/core-dom0.spec

@@ -374,6 +374,7 @@ fi
 %{python3_sitelib}/qubespolicy/cli.py
 %{python3_sitelib}/qubespolicy/agent.py
 %{python3_sitelib}/qubespolicy/gtkhelpers.py
+%{python3_sitelib}/qubespolicy/policycreateconfirmation.py
 %{python3_sitelib}/qubespolicy/rpcconfirmation.py
 %{python3_sitelib}/qubespolicy/utils.py
 %{python3_sitelib}/qubespolicy/graph.py
@@ -386,6 +387,7 @@ fi
 %{python3_sitelib}/qubespolicy/tests/rpcconfirmation.py
 
 %dir %{python3_sitelib}/qubespolicy/glade
+%{python3_sitelib}/qubespolicy/glade/PolicyCreateConfirmationWindow.glade
 %{python3_sitelib}/qubespolicy/glade/RPCConfirmationWindow.glade
 
 /usr/lib/qubes/cleanup-dispvms
@@ -413,6 +415,7 @@ fi
 /etc/xen/scripts/block-snapshot
 /etc/xen/scripts/block-origin
 /etc/xen/scripts/vif-route-qubes
+%attr(2775,root,qubes) %dir /etc/qubes-rpc/policy
 %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/admin.*
 %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