From 331f6d4903e455a2da675c6741b996c9a9ecc9d5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 Aug 2022 05:56:04 +0530 Subject: [PATCH] Document remote_control_password more thoroughly Also ignore failures in custom auth functions --- docs/changelog.rst | 2 + docs/glossary.rst | 5 ++ docs/remote-control.rst | 105 ++++++++++++++++++++++++++++++++++++---- kitty/conf/types.py | 1 + kitty/remote_control.py | 13 +++-- 5 files changed, 113 insertions(+), 13 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5a7e515a8..831291a8a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -38,6 +38,8 @@ Detailed list of changes 0.26.0 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- A new option :opt:`remote_control_password` to use fine grained permissions for what can be remote controlled (:disc:`5320`) + - Reduce startup latency by ~30 milliseconds when running kittens and remote control commands inside kitty (:iss:`5159`) - A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`) diff --git a/docs/glossary.rst b/docs/glossary.rst index f7a8f43a0..1ecbc116c 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -107,6 +107,11 @@ Variables that influence kitty behavior Set this to the directory path of the kitty source code and its Python code will be loaded from there. Only works with official binary builds. +.. envvar:: KITTY_RC_PASSWORD + + Set this to a pass phrase to use the ``kitty @`` remote control command with + :opt:`remote_control_password`. + Variables that kitty sets when running child programs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/remote-control.rst b/docs/remote-control.rst index 044e6629e..a734a02a7 100644 --- a/docs/remote-control.rst +++ b/docs/remote-control.rst @@ -17,8 +17,9 @@ Start by running |kitty| as:: kitty -o allow_remote_control=yes -o enabled_layouts=tall -In order for control to work, :opt:`allow_remote_control` must be enabled in -:file:`kitty.conf`. Here we turn it on explicitly at the command line. +In order for control to work, :opt:`allow_remote_control` or +:opt:`remote_control_password` must be enabled in :file:`kitty.conf`. Here we +turn it on explicitly at the command line. Now, in the new |kitty| window, enter the command:: @@ -92,11 +93,23 @@ As you can see, it is very easy to control |kitty| using the ``kitty @`` messaging system. This tutorial touches only the surface of what is possible. See ``kitty @ --help`` for more details. -Note that in the example's above, ``kitty @`` messaging works only when run +In the example's above, ``kitty @`` messaging works only when run inside a |kitty| window, not anywhere. But, within a |kitty| window it even works over SSH. If you want to control |kitty| from programs/scripts not running -inside a |kitty| window, you have to implement a couple of extra steps. First -start |kitty| as:: +inside a |kitty| window, see the section on :ref:`using a socket for remote control ` +below. + + +Note that if all you want to do is run a single |kitty| "daemon" and have +subsequent |kitty| invocations appear as new top-level windows, you can use the +simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that. + + +.. _rc_via_socket: + +Remote control via a socket +-------------------------------- +First, start |kitty| as:: kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty @@ -108,11 +121,6 @@ command line argument to ``kitty @``. For example:: kitty @ --to unix:/tmp/mykitty ls -Note that if all you want to do is run a single |kitty| "daemon" and have -subsequent |kitty| invocations appear as new top-level windows, you can use the -simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that. - - The builtin kitty shell -------------------------- @@ -148,6 +156,83 @@ other computers (for example, over SSH) or as other users. kitty, as if you were running with :opt:`allow_remote_control` turned on. +Fine grained permissions for remote control +---------------------------------------------- + +The :opt:`allow_remote_control` option discussed so far is a blunt +instrument, granting the ability to any program running on your computer +or even on remote computers via SSH the ability to use remote control. + +You can instead define remote control passwords that can be used to grant +different levels of control to different places. You can even write your +own script to decide which remote control requests are allowed. This is +done using the :opt:`remote_control_password` option in :file:`kitty.conf`. +Let's see some examples: + +.. code-block:: conf + + remote_control_password "control colors" get-colors set-colors + +Now, using this password, you can, in scripts run the command:: + + kitty @ --password="control colors" set-colors background=red + +Any script with access to the password can now change colors in kitty using +remote control, but only that and nothing else. You can even supply the +password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the +file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly. +See :option:`kitty @ --password-file` and :option:`kitty @ --password-env`. + +The :opt:`remote_control_password` can be specified multiple times to create +different passwords with different capabilities. Run the following to get a +list of all action names:: + + kitty @ -h + +You can even use glob patterns to match action names, for example: + +.. code-block:: conf + + remote_control_password "control colors" *-colors + +If no action names are specified, all actions are allowed. + +If ``kitty @`` is run with a password that is not present in +:file:`kitty.conf`, then kitty will interactively prompt the user to allow or +disallow the remote control request. The user can choose to allow or disallow +either just that request or all requests using that password. The user's +decision is remembered for the duration of that kitty instance. + +.. _rc_custom_auth: + +Customizing authorization with your own program +____________________________________________________________ + +If the ability to control access by action names is not fine grained enough, +you can define your own Python script to examine every remote control command +and allow/disallow it. To do so create a file in the kitty configuration +directory, :file:`~/.config/kitty/my_rc_auth.py` and add the following +to :file:`kitty.conf`: + +.. code-block:: conf + + remote_control_password "testing custom auth" my_rc_auth.py + +:file:`my_rc_auth.py` should define a :code:`is_cmd_allowed` function +as shown below: + +.. code-block:: py + + def is_cmd_allowed(pcmd, window, from_socket, extra_data): + cmd_name = pcmd['cmd'] # the name of the command + cmd_payload = pcmd['payload'] # the arguments to the command + # examine the cmd_name and cmd_payload and return True to allow + # the command or False to disallow it. Return None to have no + # effect on the command. + + # The command payload will vary from command to command, see + # the rc protocol docs for details. + .. _rc_mapping: Mapping key presses to remote control commands diff --git a/kitty/conf/types.py b/kitty/conf/types.py index 54a5faf46..96686bf7d 100644 --- a/kitty/conf/types.py +++ b/kitty/conf/types.py @@ -55,6 +55,7 @@ def ref_map() -> Dict[str, str]: 'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions', 'ssh_copy_command': f'{website_url("kittens/ssh")}#ssh-copy-command', 'shell_integration': website_url("shell-integration"), + 'rc_custom_auth': f'{website_url("remote-control")}#rc-custom-auth', 'clone_shell': f'{website_url("shell-integration")}#clone-shell', 'github_discussions': 'https://github.com/kovidgoyal/kitty/discussions', } diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 18604f262..17c9ebb4e 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -70,7 +70,7 @@ def parse_cmd(serialized_cmd: str, encryption_key: EllipticCurveKey) -> Dict[str class CMDChecker: - def __call__(self, pcmd: Dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: Dict[str, Any]) -> bool: + def __call__(self, pcmd: Dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: Dict[str, Any]) -> Optional[bool]: return False @@ -114,8 +114,15 @@ class PasswordAuthorizer: if x.match(cmd_name) is not None: return True for f in self.function_checkers: - if f(pcmd, window, from_socket, extra_data): - return True + try: + ret = f(pcmd, window, from_socket, extra_data) + except Exception as e: + import traceback + traceback.print_exc() + log_error(f'There was an error using a custom RC auth function, blocking the remote command. Error: {e}') + ret = False + if ret is not None: + return ret return False