Allow restricting the remote control actions in specific windows
This commit is contained in:
parent
b81fb3c865
commit
572e920466
@ -462,12 +462,16 @@ class Boss:
|
||||
return response
|
||||
if not pcmd:
|
||||
return response
|
||||
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.allow_remote_control))
|
||||
(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:
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user