Allow restricting the remote control actions in specific windows

This commit is contained in:
Kovid Goyal 2022-08-16 07:08:51 +05:30
parent b81fb3c865
commit 572e920466
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 69 additions and 11 deletions

View File

@ -462,12 +462,16 @@ class Boss:
return response
if not pcmd:
return response
allowed_unconditionally = (
self.allow_remote_control == 'y' or (peer_id > 0 and self.allow_remote_control in ('socket-only', 'socket')) or
(window and window.allow_remote_control))
extra_data: Dict[str, Any] = {}
try:
allowed_unconditionally = (
self.allow_remote_control == 'y' or (peer_id > 0 and self.allow_remote_control in ('socket-only', 'socket')) or
(window and window.remote_control_allowed(pcmd, extra_data)))
except PermissionError:
return {'ok': False, 'error': 'Remote control disallowed by window specific password'}
if allowed_unconditionally:
return self._execute_remote_command(pcmd, window, peer_id)
q = is_cmd_allowed(pcmd, window, peer_id > 0, {})
q = is_cmd_allowed(pcmd, window, peer_id > 0, extra_data)
if q is True:
return self._execute_remote_command(pcmd, window, peer_id)
if q is None:

View File

@ -142,6 +142,27 @@ enabled in :file:`kitty.conf`). Note that any program with the right level of
permissions can still write to the pipes of any other program on the same
computer and therefore can control kitty. It can, however, be useful to block
programs running on other computers (for example, over SSH) or as other users.
See :option:`--remote-control-password` for ways to restrict actions allowed by
remote control.
--remote-control-password
type=list
Restrict the actions remote control is allowed to take. This works like
:opt:`remote_control_password`. You can specify a password and list of actions
just as for :opt:`remote_control_password`. For example::
--remote-control-password '"my passphrase" get-* set-colors'
This password will be in effect for this window only.
Note that any passwords you have defined for :opt:`remote_control_password`
in :file:`kitty.conf` are also in effect. You can override them by using the same password here.
You can also disable all :opt:`remote_control_password` global passwords for this window, by using::
--remote-control-password '!'
This option only takes effect if :option:`--allow-remote-control`
is also specified. Can be specified multiple times to create multiple passwords.
--stdin-source
@ -320,6 +341,7 @@ def load_watch_modules(watchers: Iterable[str]) -> Optional[Watchers]:
class LaunchKwds(TypedDict):
allow_remote_control: bool
remote_control_passwords: Optional[Dict[str, FrozenSet[str]]]
cwd_from: Optional[CwdRequest]
cwd: Optional[str]
location: Optional[str]
@ -384,8 +406,16 @@ def launch(
tm = boss.active_tab_manager
opts.os_window_title = get_os_window_title(tm.os_window_id) if tm else None
env = get_env(opts, active_child)
remote_control_restrictions: Optional[Dict[str, FrozenSet[str]]] = None
if opts.allow_remote_control and opts.remote_control_password:
from kitty.options.utils import remote_control_password
remote_control_restrictions = {}
for rcp in opts.remote_control_password:
for pw, rcp_items in remote_control_password(rcp, {}):
remote_control_restrictions[pw] = rcp_items
kw: LaunchKwds = {
'allow_remote_control': opts.allow_remote_control,
'remote_control_passwords': remote_control_restrictions,
'cwd_from': None,
'cwd': None,
'location': None,

View File

@ -36,6 +36,7 @@ class Launch(RemoteCommand):
hold/bool: Boolean indicating whether to keep window open after cmd exits
location/choices.first.after.before.neighbor.last.vsplit.hsplit.split.default: Where in the tab to open the new window
allow_remote_control/bool: Boolean indicating whether to allow remote control from the new window
remote_control_password/list/str: A list of remote control passwords
stdin_source/choices.none.@selection.@screen.@screen_scrollback.@alternate.@alternate_scrollback.\
@first_cmd_output_on_screen.@last_cmd_output.@last_visited_cmd_output: Where to get stdin for the process from
stdin_add_formatting/bool: Boolean indicating whether to add formatting codes to stdin

View File

@ -97,6 +97,7 @@ class PasswordAuthorizer:
def __init__(self, auth_items: FrozenSet[str]) -> None:
self.command_patterns = []
self.function_checkers = []
self.name = ''
for item in auth_items:
if item.endswith('.py'):
path = os.path.abspath(resolve_custom_file(item))

View File

@ -10,8 +10,8 @@ from contextlib import suppress
from operator import attrgetter
from time import monotonic
from typing import (
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple,
Optional, Sequence, Set, Tuple, Union
Any, Deque, Dict, FrozenSet, Generator, Iterable, Iterator, List,
NamedTuple, Optional, Sequence, Set, Tuple, Union
)
from .borders import Border, Borders
@ -441,6 +441,7 @@ class Tab: # {{{
watchers: Optional[Watchers] = None,
overlay_behind: bool = False,
is_clone_launch: str = '',
remote_control_passwords: Optional[Dict[str, FrozenSet[str]]] = None,
) -> Window:
child = self.launch_child(
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env,
@ -449,8 +450,8 @@ class Tab: # {{{
window = Window(
self, child, self.args, override_title=override_title,
copy_colors_from=copy_colors_from, watchers=watchers,
allow_remote_control=allow_remote_control, remote_control_passwords=remote_control_passwords
)
window.allow_remote_control = allow_remote_control
# Must add child before laying out so that resize_pty succeeds
get_boss().add_child(window)
self._add_window(window, location=location, overlay_for=overlay_for, overlay_behind=overlay_behind)

View File

@ -14,8 +14,8 @@ from gettext import gettext as _
from itertools import chain
from time import monotonic
from typing import (
TYPE_CHECKING, Any, Callable, Deque, Dict, Iterable, List, NamedTuple,
Optional, Pattern, Sequence, Tuple, Union
TYPE_CHECKING, Any, Callable, Deque, Dict, FrozenSet, Iterable, List,
NamedTuple, Optional, Pattern, Sequence, Tuple, Union
)
from .child import ProcessDesc
@ -462,7 +462,6 @@ global_watchers = GlobalWatchers()
class Window:
window_custom_type: str = ''
allow_remote_control: bool = False
def __init__(
self,
@ -471,7 +470,9 @@ class Window:
args: CLIOptions,
override_title: Optional[str] = None,
copy_colors_from: Optional['Window'] = None,
watchers: Optional[Watchers] = None
watchers: Optional[Watchers] = None,
allow_remote_control: bool = False,
remote_control_passwords: Optional[Dict[str, FrozenSet[str]]] = None,
):
if watchers:
self.watchers = watchers
@ -518,6 +519,25 @@ class Window:
self.screen.copy_colors_from(copy_colors_from.screen)
else:
setup_colors(self.screen, opts)
self.remote_control_passwords = remote_control_passwords
self.allow_remote_control = allow_remote_control
def remote_control_allowed(self, pcmd: Dict[str, Any], extra_data: Dict[str, Any]) -> bool:
if not self.allow_remote_control or not self.remote_control_passwords:
return False
pw = pcmd.get('password', '')
auth_items = self.remote_control_passwords.get(pw)
if pw == '!':
auth_items = None
if auth_items is None:
if '!' in self.remote_control_passwords:
raise PermissionError()
return False
from .remote_control import password_authorizer
pa = password_authorizer(auth_items)
if not pa.is_cmd_allowed(pcmd, self, False, extra_data):
raise PermissionError()
return True
@property
def file_transmission_control(self) -> 'FileTransmission':
@ -611,6 +631,7 @@ class Window:
'default_title': self.default_title,
'title_stack': list(self.title_stack),
'allow_remote_control': self.allow_remote_control,
'remote_control_passwords': self.remote_control_passwords,
'cwd': self.child.current_cwd or self.child.cwd,
'env': self.child.environ,
'cmdline': self.child.cmdline,