Skip to content

Commit

Permalink
Merge pull request #3845 from wxtim/flow-cfg-update.authentication
Browse files Browse the repository at this point in the history
flow-cfg-update.authentication
  • Loading branch information
oliver-sanders authored Oct 1, 2020
2 parents a860b1e + d7ba6a4 commit f75322b
Show file tree
Hide file tree
Showing 6 changed files with 13 additions and 129 deletions.
32 changes: 0 additions & 32 deletions cylc/flow/cfgspec/globalcfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from cylc.flow import LOG
from cylc.flow import __version__ as CYLC_VERSION
from cylc.flow.hostuserutil import get_user_home
from cylc.flow.network.authorisation import Priv
from cylc.flow.parsec.config import ParsecConfig, ConfigNode as Conf
from cylc.flow.parsec.exceptions import ParsecError
from cylc.flow.parsec.upgrade import upgrader
Expand Down Expand Up @@ -597,37 +596,6 @@
host if you have to use the *hardwired* self-identification method.
''')

# suite
with Conf('authentication', desc='''
Authentication of client programs with suite server programs can be
configured here, and overridden in suites if necessary with
:cylc:conf:`flow.cylc[cylc][authentication]`.
The suite-specific passphrase must be installed on a user's account to
authorize full control privileges (see
:ref:`ConnectionAuthentication`). In the future we plan to move to a
more traditional user account model so that each authorized user can
have their own password.
'''):
# Allow owners to grant public shutdown rights at the most, not full
# control.
Conf(
'public',
VDR.V_STRING,
default=Priv.STATE_TOTALS.name.lower().replace('_', '-'),
options=[
level.name.lower().replace('_', '-')
for level in [
Priv.IDENTITY, Priv.DESCRIPTION,
Priv.STATE_TOTALS, Priv.READ, Priv.SHUTDOWN
]
],
desc='''
This sets the client privilege level for public access - i.e.
no suite passphrase required.
'''
)

# suite
with Conf('suite servers', desc='''
Configure allowed suite hosts and ports for starting up (running or
Expand Down
18 changes: 1 addition & 17 deletions cylc/flow/cfgspec/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

from cylc.flow import LOG
from cylc.flow.parsec.exceptions import UpgradeError
from cylc.flow.network.authorisation import Priv
from cylc.flow.parsec.config import ParsecConfig, ConfigNode as Conf
from cylc.flow.parsec.OrderedDict import OrderedDictWithDefaults
from cylc.flow.parsec.upgrade import upgrader
Expand Down Expand Up @@ -270,22 +269,6 @@
with Conf('reference test'):
Conf('expected task failures', VDR.V_STRING_LIST)

with Conf('authentication'):
# Allow owners to grant public shutdown rights at the most, not
# full control.
Conf(
'public',
VDR.V_STRING,
default=Priv.STATE_TOTALS.name.lower().replace('_', '-'),
options=[
level.name.lower().replace('_', '-')
for level in [
Priv.IDENTITY, Priv.DESCRIPTION,
Priv.STATE_TOTALS, Priv.READ, Priv.SHUTDOWN
]
]
)

with Conf('scheduling', desc='''
This section allows cylc to determine when tasks are ready to run.
'''):
Expand Down Expand Up @@ -1306,6 +1289,7 @@ def upg(cfg, descr):
u.obsolete('7.8.1', ['cylc', 'events', 'reset timer'])
u.obsolete('7.8.1', ['cylc', 'events', 'reset inactivity timer'])
u.obsolete('7.8.1', ['runtime', '__MANY__', 'events', 'reset timer'])
u.obsolete('8.0.0', ['cylc', 'authentication'])
u.obsolete('8.0.0', ['cylc', 'log resolved dependencies'])
u.obsolete('8.0.0', ['cylc', 'reference test', 'allow task failures'])
u.obsolete('8.0.0', ['cylc', 'reference test', 'live mode suite timeout'])
Expand Down
58 changes: 3 additions & 55 deletions cylc/flow/network/authorisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Network authorisation layer."""

from enum import IntEnum
from functools import wraps

from cylc.flow import LOG


class Priv(IntEnum):
"""Cylc privilege levels.
In Cylc configurations use the lower-case form of each privilege level
e.g. ``control`` for ``Priv.CONTROL``.
These levels are ordered (by the integer associated with each) from 0.
Each privilege level grants access to the levels below it.
"""

CONTROL = 6
"""Provides full control of a suite."""

SHUTDOWN = 5 # (Not used yet - for the post-passphrase era.)
"""Allows issuing of the shutdown command."""

READ = 4
"""Permits read access to the suite's state."""

STATE_TOTALS = 3
"""Provides access to the count of tasks in each state."""

DESCRIPTION = 2
"""Permits reading of suite metadata."""

IDENTITY = 1
"""Provides read access to the suite name, owner and Cylc version."""

NONE = 0
"""No access."""

@classmethod
def parse(cls, key):
"""Obtain a privilege enumeration from a string."""
return cls.__members__[key.upper().replace('-', '_')]


def authorise(req_priv_level):
def authorise():
"""Add authorisation to an endpoint.
This decorator extracts the `user` field from the incoming message to
determine the client's privilege level.
Args:
req_priv_level (cylc.flow.network.Priv): A privilege level for the
method.
Wrapped function args:
user
The authenticated user (determined server side)
Expand All @@ -86,20 +43,11 @@ def _authorise(self, *args, user='?', meta=None, **kwargs):
host = meta.get('host', '?')
prog = meta.get('prog', '?')

usr_priv_level = self._get_priv_level(user)
if usr_priv_level < req_priv_level:
LOG.warn(
"[client-connect] DENIED (privilege '%s' < '%s') %s@%s:%s",
usr_priv_level, req_priv_level, user, host, prog)
raise Exception('Authorisation failure')
# Hardcoded, for new - but much of this functionality can be
# removed more swingingly.
LOG.info(
'[client-command] %s %s@%s:%s', fcn.__name__, user, host, prog)
return fcn(self, *args, **kwargs)

# add authorisation level to docstring
_authorise.__doc__ += (
f'Authentication:\n{" " * 12}'
f':py:obj:`{__loader__.name}.{str(req_priv_level)}`\n'
)
return _authorise
return wrapper
32 changes: 7 additions & 25 deletions cylc/flow/network/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Server for suite runtime API."""

import getpass
import getpass # noqa: F401
from queue import Queue
from textwrap import dedent
from time import sleep
Expand All @@ -24,9 +24,8 @@
import zmq

from cylc.flow import LOG
from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.network import encode_, decode_, ZMQSocketBase
from cylc.flow.network.authorisation import Priv, authorise
from cylc.flow.network.authorisation import authorise
from cylc.flow.network.graphql import (
CylcGraphQLBackend, IgnoreFieldMiddleware, instantiate_middleware
)
Expand Down Expand Up @@ -254,30 +253,13 @@ def _receiver(self, message):

return {'data': response}

def _get_public_priv(self):
"""Return the public privilege level of this suite."""
if self.schd.config.cfg['cylc']['authentication']['public']:
return Priv.parse(
self.schd.config.cfg['cylc']['authentication']['public'])
return Priv.parse(glbl_cfg().get(['authentication', 'public']))

def _get_priv_level(self, user):
"""Return the privilege level for the given user for this suite."""
if user == getpass.getuser():
return Priv.CONTROL
if self.public_priv is None:
# cannot do this on initialisation as the suite configuration has
# not yet been parsed
self.public_priv = self._get_public_priv()
return self.public_priv

def register_endpoints(self):
"""Register all exposed methods."""
self.endpoints = {name: obj
for name, obj in self.__class__.__dict__.items()
if hasattr(obj, 'exposed')}

@authorise(Priv.IDENTITY)
@authorise()
@expose
def api(self, endpoint=None):
"""Return information about this API.
Expand Down Expand Up @@ -310,7 +292,7 @@ def api(self, endpoint=None):
return '%s\n%s' % (head, tail)
return 'No method by name "%s"' % endpoint

@authorise(Priv.READ)
@authorise()
@expose
def graphql(self, request_string=None, variables=None):
"""Return the GraphQL scheme execution result.
Expand Down Expand Up @@ -353,7 +335,7 @@ def graphql(self, request_string=None, variables=None):
return errors
return executed.data

@authorise(Priv.READ)
@authorise()
@expose
def get_graph_raw(self, start_point_string, stop_point_string,
group_nodes=None, ungroup_nodes=None,
Expand Down Expand Up @@ -418,7 +400,7 @@ def get_graph_raw(self, start_point_string, stop_point_string,
ungroup_all=ungroup_all)

# UIServer Data Commands
@authorise(Priv.READ)
@authorise()
@expose
def pb_entire_workflow(self):
"""Send the entire data-store in a single Protobuf message.
Expand All @@ -431,7 +413,7 @@ def pb_entire_workflow(self):
pb_msg = self.schd.data_store_mgr.get_entire_workflow()
return pb_msg.SerializeToString()

@authorise(Priv.READ)
@authorise()
@expose
def pb_data_elements(self, element_type):
"""Send the specified data elements in delta form.
Expand Down
1 change: 1 addition & 0 deletions tests/functional/deprecations/01-cylc8-basic.t
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ TEST_NAME=${TEST_NAME_BASE}-cmp
cylc validate -v "${SUITE_NAME}" 2>&1 \
| sed -n -e 's/^WARNING - \( \* (.*$\)/\1/p' > 'val.out'
cmp_ok val.out <<__END__
* (8.0.0) [cylc][authentication] - DELETED (OBSOLETE)
* (8.0.0) [cylc][log resolved dependencies] - DELETED (OBSOLETE)
* (8.0.0) [cylc][reference test][allow task failures] - DELETED (OBSOLETE)
* (8.0.0) [cylc][reference test][live mode suite timeout] - DELETED (OBSOLETE)
Expand Down
1 change: 1 addition & 0 deletions tests/functional/deprecations/01-cylc8-basic/flow.cylc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[cylc]
log resolved dependencies =
abort if any task fails =
authentication =
[[reference test]]
allow task failures =
live mode suite timeout =
Expand Down

0 comments on commit f75322b

Please sign in to comment.