4.2. Plugin development
Follow the steps in Getting started to set up the correct Python environment. There are some administrative
steps bellow to describe the plugin. After adding some code to the plugin, the directory can be packaged in a ZIP file
and uploaded to SPS.
4.2.1. Administrative tasks
Update the following files with information correct for your plugin:
MANIFEST
: plugin name, descriptions, etc. For details consult the Creating custom Authentication and Authorization
plugins developer’s guide.
Pipfile
: describes what additional Python3 PIP packages to package in the eventual plugin and also what packages to
use at development time. Note that the Plugin SDK should not be listed in the [packages] directive, as the Plugin SDK
is pre-installed on Safeguard for Privileges Sessions, on the other hand make sure that the correct Plugin SDK version
is selected in [dev-packages].
4.2.2. Basic functionality
In the lib/plugin.py
file, there is a simple skeleton code. The methods
AAPlugin.do_authenticate
,
AAPlugin.do_authorize
and
AAPlugin.do_session_ended
are called by the
AAPlugin base class after common
functionality is executed and parameters of the plugin invocation are pre-processed. All information about the
connection and pre-processing will be presented as attributes on self
in these functions.
The expected return values are defined in the technical document Creating custom Authentication and Authorization
plugins. For simplicity one can use AAResponse
class
to create the return value.
#!/usr/bin/env pluginwrapper3
from safeguard.sessions.plugin import AAPlugin, AAResponse
class Plugin(AAPlugin):
def do_authenticate(self):
return AAResponse.deny('the reason to deny')
def do_authorize(self):
return AAResponse.accept('the reason to accept')
def do_session_ended(self):
pass
4.2.3. Pre-defined attributes on self
The following attributes are available in all the above methods, except where otherwise noted.
self.connection
which is a read-only object to show
a record of the SPS connection that is being processed. For example to find out the protocol used in the connection,
write self.connection.protocol
. Note: only ‘session_id’ is available in
do_session_ended
method.
Also ‘target’ related values may not be available in
do_authenticate
method due to protocol/environmental reasons.
self.cookie
represents the cookie passed to and
returned by the plugin.
self.session_cookie
represents the session
cookie passed to and returned by the plugin.
self.username
contains the effective (gateway) user
name for this connection.
self.mfa_identity
contains the identity to be
used when contacting the MFA service.
self.mfa_password
contains the password acquired
from the user to be used in multi factor authentication.
self.mfa_password
may contain empty string to
indicate that the user requires push notification.
Note: only available in do_authenticate
method.
4.2.4. Asking the end-user
If the plugin needs extra information such as reponse to a challenge question, then the
AAPlugin.do_authenticate
method should return a value created by
AAResponse.need_info
function.
#!/usr/bin/env pluginwrapper3
from safeguard.sessions.plugin import AAPlugin, AAResponse
class Plugin(AAPlugin):
def do_authenticate(self):
if 'magic' not in self.connection.key_value_pairs:
return AAResponse.need_info("What's the magic word?", "magic")
if self.connection.key_value_pairs['magic'] == "please":
return AAResponse.accept()
else:
return AAResponse.deny()
4.2.5. Using cookies
It is possible to directly read and write self.cookie
dictionary. The contents will be retained between invocations of the plugin.
It is also possible to add a cookie value on the fly when returning with a verdict from
AAPlugin.do_authenticate
or AAPlugin.do_authorize
by using the
AAResponse.with_cookie
method:
#!/usr/bin/env pluginwrapper3
from safeguard.sessions.plugin import AAPlugin, AAResponse
class Plugin(AAPlugin):
def do_authenticate(self):
return AAResponse.accept().with_cookie(dict(somekey='somevalue'))
A more sophisticated way is to define attributes with the
@cookie_property
decorator.
The same things apply to self.session_cookie
.
4.2.6. Setting gateway user and groups
AAPlugin shall set the gateway user and gateway groups automatically in case
self.username
is different from
self.connection
.gateway_user. In other words,
if AAPlugin detects that the authenticated user is different from what SPS thinks is the gateway user, then AAPlugin
overwrites the SPS gateway user.
In order to modify the gateway user and groups of the session from
AAPlugin.do_authenticate
, use the
AAResponse.with_gateway_user
method.
Using AAResponse.with_gateway_user
method overwrites the automatic gateway user setting.
#!/usr/bin/env pluginwrapper3
from safeguard.sessions.plugin import AAPlugin, AAResponse
class Plugin(AAPlugin):
def do_authenticate(self):
return AAResponse.accept().with_gateway_user('some-user')
4.2.8. Avoiding costly calculations
To avoid redoing costly calculations or communication with external systems, it is advisable to store results of
such calculations in attributes decorated with
@cookie_property
decorator. This might be necessary if
the plugin returns “need info” to SPS, thus starting a new instance and execution of the plugin when the reply arrives.
4.2.9. Adding to the constructor
To enhance the class constructor, one should simply copy paste the
__init__
function
and add new functionality. Do keep the original call to super().__init__
at the top.
4.2.10. Authentication cache
There is an authentication cache implemented, which allows bypassing the multi factor authentication if the client IP
address and gateway username matches the previous login inside a certain time frame. This is inherently unsafe as the
IP address can be spoofed and the attacker only needs to get the gateway password of the user.
[authentication_cache]
; hard_timeout=90
; soft_timeout=15
; reuse_limit=0
Here hard_timeout
is the maximum number of seconds that the cache is valid for. The soft_timeout
can be
set to force re-authentication if the user does not reuse the cache quickly enough. And finally reuse limit is the
number of times the cache can be reused. The default for reuse_limit
is 0, which means that the authentication
cache is turned off. In the example, if reuse limit is for example 10, and the user successfully authenticated with
multi factor authentication, then the next 10 authentication are bypassed in the next 90 seconds, provided that
there is no gap bigger than 15 seconds between them.
4.2.11. Altering the steps
The calls from SPS will be translated to discrete steps by
AAPlugin
. It is possible to alter the list of
steps. For example to add self.mystep
before do_authenticate()
, which is the last step:
class MyPlugin(AAPlugin):
def _authentication_steps(self):
steps = list(super()._authentication_steps())
steps.insert(len(steps)-1, self.mystep)
return steps
def mystep(self):
pass
For example to add a step called self.mystep
before self._transform_username
:
class MyPlugin(AAPlugin):
def _authentication_steps(self):
steps = list(super()._authentication_steps())
steps_names = [item.__name__ for item in steps]
index_of_transform_username = steps_names.index('_transform_username')
steps.insert(index_of_transform_username, self.mystep)
return steps
def mystep(self):
pass