Skip to content

Access Control #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions docs/agents/access_director.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
.. highlight:: rst

.. _access_director:

=====================
Access Director Agent
=====================

The Access Director Agent distributes Access Control information
(passwords) to all subscribed agents in the OCS instance. See the
main article on :ref:`access_control_user`, and the Agent
:ref:`access_director_description` below.


.. argparse::
:module: ocs.agents.access_director.agent
:func: make_parser
:prog: agent.py



Configuration File Examples
---------------------------

OCS Site Config
```````````````

To configure the Access Director Agent we need to add an AccessDirector
block to our ocs configuration file. Here is an example configuration
block using all of the available arguments::

{'agent-class': 'AccessDirector',
'instance-id': 'access-dir',
'arguments': [['--config-file', '/config/access-director.yaml']]}

Docker Compose
``````````````

The Access Director Agent can also be run in a Docker container. An
example docker-compose service configuration is shown here::

ocs-access-dir:
image: simonsobs/ocs:latest
hostname: ocs-docker
environment:
- LOGLEVEL=info
- INSTANCE_ID=access-dir
volumes:
- ${OCS_CONFIG_DIR}:/config:ro

.. _access_director_config_file:

Access Director Configuration File
``````````````````````````````````

The format is described below, but for a browsable schema see
:class:`ocs.access.AccessDirectorConfig`.

Here is an example configuration file:

.. code-block:: yaml

# Policy file for OCS Access Directory Agent

passwords_block_default_hashfunc: none
distrib_hashfunc: md5

passwords:
- default: true
password_4: 'superuserPassword!'
- agent_class: 'FakeDataAgent'
instance_id: '!faker4'
password_2: 'fake2ser'
- instance_id: 'faker4'
password_2: 'specialLevel2'
password_3: 'speciallevel3'

exclusive_access_blocks:
- name: "the-fakers"
password: "lockout-test"
grants:
- instance_id: "faker4"
lock_levels: [1,2,3]
cred_level: 1
- instance_id: "faker*,!faker4"
lock_levels: [3]
cred_level: 3


The ``passwords`` entry is a list of password assignment blocks, which
define passwords that should grant clients certain credential levels
on certain agents.

The syntax of the assignment blocks is described in
:class:`ocs.access.AccessPasswordItem`.

**Examples**

Example 1 -- set the level (1,2,3,4) passwords for all agents to ('', 'special2',
'special3', 'superuser')::

passwords:
- default: true
password_1: ''
password_2: 'special2'
password_3: 'special3'
password_4: 'superuser'

Example 2 -- like Example 1 except that any agent with the class
"FakeDataAgent" will have level 2 password set to 'fake2'::

passwords:
- default: true
password_1: ''
password_2: 'special2'
password_3: 'special3'
password_4: 'superuser'
- agent_class: FakeDataAgent
password_2: 'fake2'

Example 3 -- like Example 2 except that the agent with `instance_id`
of "danger4" will have the level 2 and level 3 access totally
disabled (even if "danger4" is a FakeDataAgent.)::

passwords:
- default: true
password_1: ''
password_2: 'special2'
password_3: 'special3'
password_4: 'superuser'
- agent_class: FakeDataAgent
instance_id: '!danger4'
password_2: 'fake2'


**exclusive_access_blocks**

The ``exclusive_access_blocks`` entry is a list of access grant
blocks. Each block must at least have a (unique) "name" entry, and a
list of :class:`GrantConfigItem<ocs.access.GrantConfigItem>`. Each
GrantConfigItem targets some set of agent instances (using the
`default` / `agent_class` / `instance_id` keys in the same way that
:class:`ocs.access.AccessPasswordItem` does). The `cred_level`
declares what level to give the grantee, on the targeted instances.
The `lockout_levels` is a list of Credential Levels to *lock out*,
during this grant.

The additional settings items are:

- ``passwords_block_default_hashfunc``: name of the hash function to
assume for passwords provided in the "passwords" block. (Default:
"none", meaning they are the cleartext.)
- ``distrib_hashfunc``: hashfunc to use, instead of cleartext, when
distributing passwords to agents. (Does not affect passwords that
were provided already hashed.)



.. _access_director_description:


Description
-----------

The role of this Agent is to distribute Access Control information to
Agent Instances and Clients in the OCS instance. In an OCS there will
normally be zero or one instance of the Access Director (but it's
possible to set up multiple instances, and have different agents
attuned to different Access Directors).

The agent configuration is provided through the Access Config File,
the path to which is a command-line parameter. The ``manager``
process distributes access information to agents on a special feed
(``...feeds.controls``). The task ``reload_config`` may be used to
trigger a reload of the config file. If there are syntax errors in
the config file, this will normally cause the reload to be ignored and
the existing configuration to persist.

Two "special access points" are exposed by the Access Director. The
``agent_poll`` method is used by agents to request an update of their
current access information; they will normally do this when they
connect or reconnect to crossbar.

The ``request_exclusive`` access point is used by clients that wish to
establish an Exclusive Access Lock. The client would provide the name
of the access block, and a password (if defined). The Agent then
returns to that client a randomly generated password that the client
can use to talk to all agents covered by the grant. Starting then,
and until the grant expires or is released, the Agent distributes
updated access information to all agents that reflects the new access
rules -- i.e. special access levels, granted based on that password,
and also any lock-outs that are associated with the grant.


Agent API
---------

.. autoclass:: ocs.agents.access_director.agent.AccessDirector
:members:
17 changes: 17 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ agents
The ``agents/`` directory contains the OCS Agents, as well as any supporting
code.

agents.access_director
``````````````````````

.. automodule:: ocs.agents.access_director.agent
:members:
:undoc-members:
:show-inheritance:
:noindex:

agents.aggregator
`````````````````

Expand Down Expand Up @@ -87,6 +96,14 @@ ocs.agent_cli
:undoc-members:
:show-inheritance:

ocs.access
----------

.. automodule:: ocs.access
:members:
:undoc-members:
:show-inheritance:

.. _ocs_base_api:

ocs.base
Expand Down
165 changes: 165 additions & 0 deletions docs/developer/access_control.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
.. _access_control_dev:

Access Control
==============

This section discusses the implementation of Access Control in Agent
code. A connected Client will provide a password to the operation,
and this will automatically be compared to the set of accepted
passwords to establish what Privilege Level to use.

From the perspective of the Agent code, there are two ways access can
be restricted:

1. When the Task or Process is registered, on Agent start-up, pass
``min_privs=2`` or ``min_privs=3``. Then, when a client tries to
start or stop the Operation, any provided password will
automatically be checked, and the request will be immediately
rejected.

2. In the body of the Task or Process, the agent code can check the
current privilege level, and take different actions depending on
that level. This is appropriate when certain conditions, which
need to be assessed on the fly, might warrant requiring a higher
level of privilege to run.

The next two sections go into more detail on those two approaches.


Restriction of Operations at Agent Start-up
-------------------------------------------

The simplest form of restriction is to simply require a minimum
privilege level for access to a Task or Process.

To restrict access to the Operation Process, state the required access
level in the ``min_privs`` argument when registering the op. For
example::

agent.register_task('deep_clean', washingmachine.deep_clean,
min_privs=2)

If the need for Access Control depends somewhat on the particular
instance of an Agent, it may be convenient to have ``min_privs`` set
based on a command-line parameter. The FakeData Agent shows an
example of this.

When restrictions are set up in this way, checking of privileges is
handled automatically and immediately when a client calls ``start`` or
``stop`` on an operation -- if the privileges are not sufficient the
client gets an *immediate* error response indicating the failure.

In the case that an Agent has privilege levels coded in, but you want
to disable (or effectively disable) those restrictions, there are two
simple options:

1. You can run tell the agent to run with no privilege enforcement, by
passing `--access-policy none`. In the SCF that would look like
this::

{'agent-class': 'FakeDataAgent',
'instance-id': 'faker4',
'arguments': [['--access-policy', 'none']],
},


2. You can add an Access Director configuration block, that accepts an
empty password for access to level 3::

passwords:
- instance_id: 'faker4'
- password_3: ''


Note that in case (1), the agent will entirely ignore the Access
Director -- so Exclusive Access grants will also not have any effect
on this agent. In case (2) such grants will still be respected (if
they include a lock-out for general level 3 access).


Dynamic Access Restrictions
---------------------------

A more complex imposition of restrictions is to make run-time
decision, in code. This is achieved, within the body of the Task or
Process function, by checking ``session.cred_level``.

E.g.::

class WashingMachine(OCSAgent):
# ...

def deep_clean(self, session, args):
"""deep_clean(force=False)

**Task** -- Perform a deep clean cycle.

If a normal wash cycle is in progress, cred_level=2 is
required to start the deep_clean (which will cause the
current wash cycle to be aborted); in that case force=True
must be passed.

"""
if self.wash_cycle_in_progress:
if session.cred_level < 2 or not force:
return False, ("CredLevel=2 and force=True are required "
"to start deep_clean during a wash cycle.")
self._abort_wash_cycle()

self.washer._hardware.initiate_deep_clean()
...


Note that rejections in the function body cause the Operation to exit
with error, rather than for the ``start`` call of the Operation to
return an immediate error.

It is good practice for an operation to not have drastically different
behavior, depending *only* on the credential level. Users/clients may
sometimes provide their high-privilege credentials to routine
operations, and safe-guards should remain in place despite that
privileged access. This is the reason for requiring ``force=True`` in
the ``deep_clean`` example, above -- even a high-privilege user
probably doesn't want, accidentally, to run ``deep_clean`` while the
``wash_cycle_in_progress`` is.


Choosing What to Protect
------------------------

When developing agents with Access Control in mind, you should
consider what functionality of the agent should be restricted. In a
complex system, operation by users can become very awkward if numerous
different passwords are required to access standard functionality of
various devices. We thus recommend that Access Control be used only
to guard against the accidental entry into unsafe or highly
inconvenient hardware states.

As a general guideline:

- Require privilege level 3 for operations that could lead to damage,
long-term outages, or degrade observatory safety.
- Require privilege level 2 for activities that could lead to awkward
device states that might delay observatory function temporarily, or
require expert attention to recover from.
- Use default privilege level (1) for everything else. This is true,
even if some expertise is required to use the device properly.

Testing and Debugging
---------------------

When testing an agent's Access Control, recall that the
``--access-policy`` argument can be used to set the level 2 and 3
passwords, independent of whether an Access Director agent is running
in the OCS instance. An example SCF entry for a FakeData agent with
passwords is::

{'agent-class': 'FakeDataAgent',
'instance-id': 'faker4',
'arguments': [['--access-policy', 'override:fake-pw-2,fake-pw-3']],
},

You can override the ``--access-policy`` on the command line when
using ``ocs-agent-cli``; e.g.::

$ ocs-agent-cli --instance-id=faker4 --access-policy=none
Loading