qrexec.policy.parser – Qubes RPC policy parser

Qrexec policy format is available as separate specification: Multifile policy.

Representing domain names

class qrexec.policy.parser.VMToken(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]

A domain specification

Wherever policy evaluation needs to represent a VM or a @something token, instances of this class (and subclasses) are used. Each @token has its own dedicated class.

There are 4 such contexts:
  • Source: for whatever was specified in policy in 3rd column

  • Target: 4th column in policy

  • Redirect: target= parameter to Allow and Ask, and default_target= for the latter

  • IntendedTarget: for what user invoked the call for

Not all @tokens can be used everywhere. Where they can be used is specified by inheritance.

All tokens are also instances of str and can be compared to other strings.

is_special_value() bool[source]

Check if the token specification is special (keyword) value

match(other: str, *, system_info: FullSystemInfo, source: VMToken | None = None) bool[source]

Check if this token matches opposite token

property text: str

Text of the token, without possibly ‘@’ prefix

property type: str

Type of the token

'keyword' for special values, 'name' for qube name

class qrexec.policy.parser.Source(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]
class qrexec.policy.parser.Target(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]
class qrexec.policy.parser.Redirect(value: str | None, *, filepath: Path | None = None, lineno: int | None = None)[source]
class qrexec.policy.parser.IntendedTarget(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]
verify(*, system_info: FullSystemInfo) VMToken | None[source]

Check if given value names valid target

This function check if given value is not only syntactically correct, but also if names valid service call target (existing domain, or valid '@dispvm' like keyword). If the domain does not exist, returns a DefaultVM.

Parameters:

system_info – information about the system

Returns:

for successful verification

Return type:

VMToken

Raises:

qrexec.exc.AccessDenied – for failed verification

The classes should be instantiated using either VMToken or the context that expects the token.

>>> type(VMToken('@adminvm'))
<class 'qrexec.policy.parser.AdminVM'>
>>> type(Target('@adminvm'))
<class 'qrexec.policy.parser.AdminVM'>

The latter has the advantage that tokens inappropriate for the context are rejected:

>>> Redirect('@tag:tag1')
Traceback (most recent call last):
...
qrexec.exc.PolicySyntaxError: <unknown>:None: invalid redirect token: '@tag:tag1'

The tokens are as follows. EXACT means the token should match exactly. PREFIX means anything goes after the prefix. When two different prefixes match ('@dispvm:'/'@dispvm:@tag:'), the longer one is chosen.

class qrexec.policy.parser.AdminVM(value: str | None, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Source, Target, Redirect, IntendedTarget

EXACT = '@adminvm'
class qrexec.policy.parser.AnyVM(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Source, Target

EXACT = '@anyvm'
class qrexec.policy.parser.DefaultVM(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Target, IntendedTarget

EXACT = '@default'
class qrexec.policy.parser.TypeVM(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Source, Target

PREFIX = '@type:'
class qrexec.policy.parser.TagVM(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Source, Target

PREFIX = '@tag:'
class qrexec.policy.parser.DispVM(value: str | None, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Target, Redirect, IntendedTarget

EXACT = '@dispvm'
static get_dispvm_template(source: str, *, system_info: FullSystemInfo) DispVMTemplate | None[source]

Given source, get appropriate template for DispVM. Maybe None.

class qrexec.policy.parser.DispVMTemplate(value: str | None, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Source, Target, Redirect, IntendedTarget

PREFIX = '@dispvm:'
class qrexec.policy.parser.DispVMTag(token: str, *, filepath: Path | None = None, lineno: int | None = None)[source]

Bases: Source, Target

PREFIX = '@dispvm:@tag:'

There is a helper metaclass for this, do not use it elsewhere:

class qrexec.policy.parser.VMTokenMeta(name, bases, namespace, /, **kwargs)[source]

Request object

class qrexec.policy.parser.Request(service: str | None, argument: str, source: str, target: str, *, system_info: FullSystemInfo, allow_resolution_type: Type[AllowResolution] = <class 'qrexec.policy.parser.AllowResolution'>, ask_resolution_type: Type[AskResolution] = <class 'qrexec.policy.parser.AskResolution'>, requested_source: str | None = '')[source]

Qrexec request

A request object keeps what is searched for in the policy. It keeps the principal quadruple: service, argument, source and target that are parameters of the qrexec call. There is also system_info, which represents current state of the system, incl. the list of all domains in the system and their respective properties that are relevant to policy.

Parameters:
  • service (str or None) – Service name.

  • argument (str) – The argument. Must start with '+'.

  • source (str) – name of source qube

  • target (str) – target designation

  • system_info (dict) – as returned from qrexec.utils.system_info()

  • allow_resolution_type (type) – a child of AllowResolution

  • ask_resolution_type (type) – a child of AskResolution

service

the service that is being requested

argument

argument for the service

requested_source

requested source qube name (from qrexec-client-vm)

target

target (qube or token) as requested by source qube

system_info

system info

allow_resolution_type

factory for allow resolution

ask_resolution_type

factory for ask resolution

source

source qube name

Actions and resolutions

There are two things that represent “what to do” when there is a match in policy: actions and resolutions. Action is part of a Rule, it means what this rule prescripts. In contrast, a resolution is something that happens after a Rule was actually matched to Request.

class qrexec.policy.parser.ActionType(rule: Rule)[source]

Base class for actions

Children of this class are types of objects representing action in policy rule (Rule.action). Not to be confused with AbstractResolution, which happens when particular rule is matched to a Request.

Keyword arguments to __init__ are taken from parsing params in the rule, so this defines, what params are valid for which action.

actual_target(intended_target: VMToken) IntendedTarget[source]

If action has redirect, it is it. Otherwise, the rule’s own target

Parameters:

intended_target (IntendedTarget) – Request.target

Returns:

either target, if not None, or

intended_target

Return type:

IntendedTarget

static allow_no_autostart(target: str, system_info: FullSystemInfo) bool[source]

Should we allow this target when autostart is disabled

abstractmethod evaluate(request: Request) AbstractResolution[source]

Evaluate the request.

Depending on action and possibly user’s decision either return a resolution or raise exception.

Parameters:

request (Request) – the request that was matched to the rule

Returns:

for successful requests

Return type:

AbstractResolution

Raises:

qrexec.exc.AccessDenied – for denied requests

rule

the rule that holds this action

class qrexec.policy.parser.Allow(rule: Rule, *, target: str | None = None, user: str | None = None, notify: bool = False, autostart: bool = True)[source]
evaluate(request: Request) AllowResolution[source]
Returns:

for successful requests

Return type:

AllowResolution

Raises:

qrexec.exc.AccessDenied – for invalid requests

class qrexec.policy.parser.Deny(rule: Rule, *, notify: bool | None = None)[source]
evaluate(request: Request) NoReturn[source]
Raises:

qrexec.exc.AccessDenied

class qrexec.policy.parser.Ask(rule: Rule, *, target: str | None = None, default_target: str | None = None, user: str | None = None, notify: bool = False, autostart: bool = True)[source]
evaluate(request: Request) AskResolution[source]
Returns:

AskResolution

Raises:

qrexec.exc.AccessDenied – for invalid requests

class qrexec.policy.parser.Action(value)[source]

Bases: Enum

Action as defined by policy

class qrexec.policy.parser.AbstractResolution(rule: Rule, request: Request, *, user: str | None)[source]

Object representing positive policy evaluation result - either ask or allow action

abstractmethod async execute() str[source]

Execute the action. For allow, this runs the qrexec. For ask, it asks user and then (depending on verdict) runs the call.

Parameters:

caller_ident (str) – Service caller ident (process_ident,source_name, source_id)

notify: bool

whether to notify the user about the action taken

request

request

rule

policy rule from which this action is derived

user

the user to run command as, or None for default

class qrexec.policy.parser.AllowResolution(rule: Rule, request: Request, *, user: str | None, target: str, autostart: bool)[source]

Resolution returned for Rule with Allow.

async execute() str[source]

Return the allowed action

classmethod from_ask_resolution(ask_resolution: AskResolution, *, target: str) AllowResolution[source]

This happens after user manually approved the call

notify: bool

whether to notify the user about the action taken

target

target domain the service should be connected to

class qrexec.policy.parser.AskResolution(rule: Rule, request: Request, *, targets_for_ask: Sequence[str], default_target: str | None, autostart: bool, user: str | None)[source]

Resolution returned for Rule with Ask.

This base class is a dummy implementation which behaves as if user always denied the call. The programmer is expected to inherit from this class and overload execute() to display the question to the user by appropriate means. User should have choice among targets_for_ask. If default_target is not None, that should be the default. Otherwise there should be no default. After querying the user, handle_user_response() should be called. For negative answers, raising qrexec.exc.AccessDenied is also enough.

The child class should be supplied as part of Request.

async execute() NoReturn[source]

Ask the user for permission.

This method should be overloaded in children classes. This implementation always denies the request.

Raises:

qrexec.exc.AccessDenied – always

handle_invalid_response() NoReturn[source]

Handle invalid response for the ‘ask’ action. Throws AccessDenied.

handle_user_response(response: bool, target: str) AllowResolution[source]

Handle user response for the ‘ask’ action. Children class’ execute() is supposed to call this method to report the user’s verdict.

Parameters:
  • response (bool) – whether the call was allowed or denied

  • target (str) – target chosen by the user (if reponse==True)

Returns:

for positive answer

Return type:

AllowResolution

Raises:

qrexec.exc.AccessDenied – for negative answer

notify: bool

whether to notify the user about the action taken

Parsers

class qrexec.policy.parser.Rule(service: str, argument: str, source: str, target: str, action: str, params: List[str], *, policy: AbstractPolicy, filepath: Path, lineno: int | None)[source]

A single line of policy file

Avoid instantiating manually, use either from_line() or from_line_service().

policy

the parser that this rule belongs to

filepath

the file path

lineno

the line number

service

the qrexec service

argument

the argument to the service

source

source specification

target

target specification

action: Allow | Deny | Ask

policy action

classmethod from_line(policy, line, *, filepath, lineno)[source]

Load a single line of qrexec policy and check its syntax. Do not verify existence of named objects.

Parameters:
  • line – a single line of actual qrexec policy (not a comment, empty line or @include)

  • filepath (pathlib.Path) – Path of the file from which this line is loaded

  • lineno – line number from which this line is loaded

Raises:

PolicySyntaxError – when syntax error is found

classmethod from_line_service(policy, service, argument, line, *, filepath, lineno)[source]

Load a single line in old format.

Parameters:
  • service – the service for which this line applies

  • argument – argument for the service

  • line (str) – the line to be parsed

  • filepath (pathlib.Path) – the file from which this line was taken

  • lineno (int) – the line number

Raises:

PolicySyntaxError – when syntax error is found

is_match(request: Request) bool[source]

Check if given request matches this line.

Parameters:

request – request to check against

Returns:

True or False

is_match_but_target(request: Request) bool[source]

Check if given (service, argument source) matches this line.

Target is ignored. This is used for collect_targets_for_ask().

Parameters:
  • system_info – information about the system - available VMs, their types, labels, tags etc. as returned by app_to_system_info()

  • service – name of the service

  • argument – the argument

  • source – name of the source VM

  • target – name of the target VM, or None if not specified

  • system_info – the context

Returns:

True or False

class qrexec.policy.parser.AbstractParser[source]

A minimal, pluggable, validating policy parser

rule_type

default rule type

alias of Rule

load_policy_file(file, filepath)[source]

Parse a policy file

load_policy_file_service(service, argument, file, filepath)[source]

Parse a policy file from !include-service

abstractmethod handle_include(included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include line when encountered in policy_load_file().

This method is to be provided by subclass.

abstractmethod handle_include_dir(included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include-dir line when encountered in policy_load_file().

This method is to be provided by subclass.

abstractmethod handle_include_service(service, argument, included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include-service line when encountered in policy_load_file().

This method is to be provided by subclass.

abstractmethod handle_rule(rule, *, filepath, lineno)[source]

Handle a line with a rule.

This method is to be provided by subclass.

abstractmethod handle_compat40(*, filepath, lineno)[source]

Handle !compat-4.0 line when encountered in policy_load_file().

This method is to be provided by subclass.

handle_comment(line, *, filepath, lineno)[source]

Handle a line with a comment

This method may be overloaded in subclass. By default, it does nothing.

class qrexec.policy.parser.AbstractPolicy(*args, **kwds)[source]

This class is a parser that accumulates the rules to form policy.

rules: List[Rule]

list of Rule objects

handle_rule(rule, *, filepath, lineno)[source]

Handle a line with a rule.

This method is to be provided by subclass.

evaluate(request)[source]

Evaluate policy

Returns:

For allow or ask resolutions.

Return type:

AbstractResolution

Raises:

AccessDenied – when action should be denied unconditionally

find_matching_rule(request)[source]

Find the first rule matching given request

collect_targets_for_ask(request)[source]

Collect targets the user can choose from in ‘ask’ action

Word ‘targets’ is used intentionally instead of ‘domains’, because it can also contains @dispvm like keywords.

class qrexec.policy.parser.AbstractFileLoader[source]

Parser that loads next files on !include[-service] directives

This class uses regular files as accessed by pathlib.Path, but it is possible to overload those functions and use file-like objects.

resolve_path(included_path: PurePosixPath) Path[source]

Resolve path from !include* to pathlib.Path

resolve_filepath(included_path: PurePosixPath, *, filepath, lineno) Tuple[TextIO, PurePath][source]

Resolve !include[-service] to open file and filepath

The callee is responsible for closing the file descriptor.

Raises:

qrexec.exc.PolicySyntaxError – when the path does not point to a file

handle_include(included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include line when encountered in policy_load_file().

This method is to be provided by subclass.

handle_include_service(service, argument, included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include-service line when encountered in policy_load_file().

This method is to be provided by subclass.

class qrexec.policy.parser.AbstractDirectoryLoader[source]

Parser that loads next files on !include-dir directives

resolve_dirpath(included_path: PurePosixPath, *, filepath, lineno) Path[source]

Resolve !include-dir to directory path

Return type:

pathlib.Path

Raises:

qrexec.exc.PolicySyntaxError – when the path does not point to a directory

handle_include_dir(included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include-dir line when encountered in policy_load_file().

This method is to be provided by subclass.

load_policy_dir(dirpath)[source]

Load all files in the directory (!include-dir)

Parameters:

dirpath (pathlib.Path) – the directory to load

Raises:

OSError – for problems in opening files or directories

class qrexec.policy.parser.AbstractFileSystemLoader(*, policy_path: None | PurePath | Iterable[PurePath] = None)[source]

This class is used when policy is stored as regular files in a directory.

Parameters:

policy_path – Load these directories. Paths given to !include etc. directives in a file are interpreted relative to the path from which the file was loaded.

resolve_path(included_path: PurePosixPath) Path[source]

Resolve path from !include* to pathlib.Path

class qrexec.policy.parser.FilePolicy(*, policy_path: None | PurePath | Iterable[PurePath] = None)[source]

Full policy loaded from files.

Usage:

>>> policy = qrexec.policy.parser.FilePolicy()
>>> request = Request(
...     'qrexec.Service', '+argument', 'source-name', 'target-name',
...     system_info=qrexec.utils.get_system_info())
>>> resolution = policy.evaluate(request)
>>> await resolution.execute('process-ident')  # asynchroneous method

Miscellaneous and test facilities

class qrexec.policy.parser.ValidateParser(*, overrides: Dict[Path, str | None], policy_path: None | PurePath | Iterable[PurePath] = None)[source]

A parser that validates the policy directory along with proposed changes.

Pass files to be overriden in the overrides dictionary, with either new content, or None if the file is to be deleted.

load_policy_dir(dirpath: Path) None[source]

Load all files in the directory (!include-dir)

Parameters:

dirpath (pathlib.Path) – the directory to load

Raises:

OSError – for problems in opening files or directories

resolve_filepath(included_path: PurePosixPath, *, filepath, lineno) Tuple[TextIO, PurePath][source]

Resolve !include[-service] to open file and filepath

The callee is responsible for closing the file descriptor.

Raises:

qrexec.exc.PolicySyntaxError – when the path does not point to a file

handle_rule(rule, *, filepath, lineno)[source]

Handle a line with a rule.

This method is to be provided by subclass.

class qrexec.policy.parser.StringLoader(*args, policy, **kwds)[source]

An in-memory loader used for tests

Parameters:

policy (dict or str) – policy dictionary. The keys are filenames to be included. It should contain '__main__' key which is loaded. If the argument is str, it behaves as it was dict’s '__main__'.

resolve_filepath(included_path, *, filepath, lineno) Tuple[TextIO, PurePath][source]
Raises:

qrexec.exc.PolicySyntaxError – when wrong path is included

handle_include_dir(included_path: PurePosixPath, *, filepath, lineno)[source]

Handle !include-dir line when encountered in policy_load_file().

This method is to be provided by subclass.

class qrexec.policy.parser.StringPolicy(*, policy, policy_compat=None, **kwds)[source]

String policy, used for tests and loading single files as policy. It can be used to test most of the code paths used in policy parsing.

>>> testpolicy = StringPolicy(policy={
...     '__main__': '!include policy2'
...     'policy2': '* * @anyvm @anyvm allow'})
rules: List[Rule]

list of Rule objects

Helper functions

qrexec.policy.parser.get_invalid_characters(s: str, allowed: FrozenSet[str] = frozenset({'+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}), disallowed: Iterable[str] = '') Sequence[str][source]

Return characters contained in disallowed and/or not int allowed

qrexec.policy.parser.parse_service_and_argument(rpcname: str | PurePath, *, no_arg: str = '+') Tuple[str, str][source]

Parse service and argument string.

Parse SERVICE+ARGUMENT. Argument may be empty (single + at the end) or omitted (no + at all). If no argument is given, no_arg is returned instead. By default this returns '+', as if argument is empty.

A Path from pathlib is also accepted, in which case the filename is parsed.

qrexec.policy.parser.validate_service_and_argument(service: str | None, argument: str | None, *, filepath: Path, lineno: int | None) Tuple[str | None, str | None][source]

Check service name and argument

This is intended as policy syntax checker to discard obviously invalid service names and arguments. There are some cases for which this function will not signal a problem, but the call still would be invalid. One of those cases is too long total call name.

Parameters:
  • service (str) – the service as appeared in policy file

  • argument (str) – the argument as appeared in policy file

  • filepath (pathlib.Path) – the file path

  • lineno (int) – the line in the file

Returns:

service and argument

Return type:

(str or None, str or None)

Raises:

qrexec.exc.PolicySyntaxError – for a number of forbidden cases

qrexec.policy.parser.filter_filepaths(filepaths: Iterable[Path]) List[Path][source]

Check if files should be considered by policy.

The file name should contain only allowed characters (latin lowercase, digits, underscore, full stop and dash). It should not start with the dot.

Only the file name is considered, not the directories on path that leads to it.

Parameters:

filepaths – the file paths

Returns:

sorted list of paths, without ignored ones

Return type:

list of pathlib.Path

Raises:

qrexec.exc.AccessDenied – for invalid path which is not ignored