Hello everyone,
I have been looking into PR #495
https://github.com/IdentityPython/pysaml2/pull/495
What the user needs is an option to configure the signing algorithm
that will be used by the SP to sign an authentication request. This
comes in hand with the digest algorithm and in extension what is
entity that should be signed.
What the proposed one line change does, is allows this value to exist
in pysaml2 Config object. By itself, it does not affect anything in
the code - it does not set the signing algorithm. It is only a
placeholder for a value. Something else is supposed to look at that
value, at the right time, and pass it as an argument to the
appropriate function/method.
This doesn't seem right. If it is in the configuration, then it should
actually do something - it should affect the way the library behaves.
Looking into this I stumbled upon some commits, made about a year ago,
that implement some of this functionality for the IdP part:
* 2aedfa0 - make both sign response and assertion configurable
adds sign_assertion and sign_response options
* bd4303a - Signing signature and digest algorithm configuration
adds sign_alg and digest_alg options
These are implemented in the SATOSA repository (see
satosa/frontends/saml2.py the _handle_authn_response method). However,
their configuration lies between the lines of the pysaml2
configuration (under service/idp/policy). This is wrong - each project
should be responsible for its own configuration. The code that decides
what should be signed and how, should live in pysaml2. If it is
handled by SATOSA then it should be part of the SATOSA configuration
and an override of the pysaml2 configuration.
Moreover, it seems that this functionality was partially already there
in pysaml2 in the first place. See the Policy class in
saml2/assertion.py and its get_sign method. An option named 'sign' can
be defined under the service/idp/policy part of the configuration,
that defines an array of values that represent what should be signed,
for example:
service:
idp:
policy:
sign:
- response
- assertion
So now we have both the above 'sign' option, plus 'sign_assertion' and
'sign_response', which should do the same thing.
What I would like to do is move the code introduced by the commits
above into pysaml2: this will allow a consistent behaviour whether
pysaml2 is used by SATOSA or some other project. Then the same options
can be used by the backend too, which would satisfy the user request.
Once that is done we can look into making the configuration work in
one way.
This is bigger than it looks. What happens now is that we define for
example, that we want to use SHA512 as a sign_alg. This will be used
when a authentication response is formed, but it is ignored when for
example a logout request is to be created. This happens because the
configuration of what signing algorithm will be used is only
implemented for the authentication response.
There are two ways to fix this:
- we either assume that a configuration option like 'sign_alg' is
global, and as such, it affects the signing algorithm of anything that
is to be signed
- or we assume that it relates to the authentication req/response only
(and in that case it shold probably be called authn_sign_alg or alike)
and require new options for other kinds of signatures
(logout_sign_alg, metadata_sign_alg, etc).
The first solution requires that we find all places in the code use
signatures and make sure they respect the configuration. I have
already noted (a lot of) entry points to pysaml2 that should be
looking into the configuration to derive the signing and digest
algorithm values.
The second option is "easier" to work with, as it allows for an
incremental implementation of this request. The second approach is
also more flexible for the end user, but at the same time more complex
as it requires more configuration values to be set.
Ofcourse we can have both, use an option like sign_alg to defined the
signing algorithm, and use "suboptions" like authn_sign_alg to
override the sign_alg setting where needed.
I hope this makes sense (even though it mixes at least four
configuration options together). If you have any comments, I'd like to
hear.
Cheers,
--
Ivan c00kiemon5ter Kanakarakis >:3
On 8/2/19 8:24 AM, Rainer Hoerbe wrote:> Hi John
>
>> Am 2019-08-01 um 18:52 schrieb John Paraskevopoulos
>> <io.paraskev at gmail.com>:
>>
>>
>> I'm not sure I understand the use case where a shared config is
>> needed.
>
> The ADFS-IDP Use Case is the requirement to redirect to some service
> to complete role selection or any other interaction that would
> change the set of attributes in the assertion. The solution is to
> replay the AuthnRequest to the IDP after the user selected the role.
>
> I implemented this by 2 cooperative microservices, one Request-MS
> saving the AuthnRequest and one Reponse-MS replaying it. There are a
> few of shared parameters fron the config, and you need to know the
> own backend entityID as well.
>
>From what I understand and looked briefly about the ADFS-IDP use case,
this sound more like a need for a custom saml2 backend/frontend than
microservices since we're talking about a need to establish a custom
re-routing before completing the assertion. It might be better since
we're talking about a specific use case that is the exception to the
classic workflow.
>>
>> I also believe that Singleton is an anti-pattern and should be
>> avoided unless absolutely needed (i.e mostly logging). Let's not
>> forget that this pattern was invented ~25 years ago where things
>> and resources were really different. In the case we're discussing
>> here, I think that the configuration is not a cross cutting
>> concern, and even if it is, we should try to fix this instead of
>> accepting it and trying to come up with a singleton. Making the
>> config globally available to everyone would hide the exact
>> dependencies of the components who call it (because the would
>> consume the whole config instead of some specific data) breaking
>> the separation of concerns concept. This would mean that we won't
>> be sure which config options we could change without breaking
>> things. Just because Django uses a single settings file doesn't
>> mean we should as well (firstly because Django is a different kind
>> of framework and secondly because Django doesn't mean perfect
>> design. An object should have all the data it needs, no more no
>> less and these should be clearly declared as dependency. This can
>> be achieved either as data parameter during initialization or
>> through a config Factory that will take care of the data processing
>> and return the needed information for each component (although this
>> in a way hides the config dependency it is still more preferable
>> than the singleton in my opinion).
>>
>> Currently from what I've seen and with my little experience on the
>> project's internals, there is some separation of concerns by having
>> different config files for different components. If we believe some
>> options from different components should be used by other
>> components as well we should first examine why this is happening
>> (maybe the use case scenario is wrong or maybe we need to redesign
>> things) and then see what needs fixing and how.
>
> These are valid arguments. But how would you avoid duplication of
> config elements? Adding some reference mechanism in yaml seems too
> complex to me. Explicitly passing additional config files where the
> ms is configured seem possible may be possible, but is extra
> overhead. It is considered quit pythonic to access variables in
> objects without strict scope definitions, just using the underscore
> prefix by convention. In that sense my opinion is that dependencies
> may be less explicit to trade off for convenience.
>
Although as I've said, I don't like the term "pythonic", it is not
considered pythonic to access variables/methods of objects that have an
underscore prepended. Even though Python doesn't have a strict enforcing
rule about private variables and methods, the current convention is to
use a single underscore (double is used for name mangling, and double
prepended and appended for system defined magic) to define class
internal stuff. Internal stuff mean that they are not part of the public
interface (that's why help(<class name>) doesn't show them) and by not
being part of the public interface means that you are not supposed to
rely on them (because they can freely change without notice unlike
public methods and variables). So it's not pythonic but even if it was,
it would be against the OO convention about public/private
methods/variables.
On 8/2/19 12:39 AM, Giuseppe De Marco wrote:
> Hi John, What you wrote make sense.
>
> What do you think about the possibility to move in
> satosa.satosa_config those lines of code [1]?
>
> This way we would import satosa_config where needed and nothing
> else.
This would probably create a racing condition type. During the
initialization, a specific config file is loaded and passed to the
creation of the plugins etc. If we did something like this, then if a
microservice decides to load on its own the config file, how would you
be sure that the config file hasn't changed? This would mean that in the
core app there would be static loading (once during initialization), but
for specific plugins/microservices, there would be dynamic loading when
they would run. There is more danger than value in having multiple
different instances of a config file. Plus it doesn't change the fact,
that we would still be getting more information in our objects than we
need (since we obviously don't need the whole config info)
>
> What do you think also about the shared session? During the meeting
> on july 9th Ivan proposed an abstract class to handle shared
> sessions, he proposed many usable storage backends, like cookies,
> RDBMS, nosql and possibly other.
I'm not sure about what kind of shared session (and for what) you are
talking. Unfortunately I couldn't join the meeting on July 9th so I
can't express any opinion about that.
>
>
> [1]
> https://github.com/IdentityPython/SATOSA/blob/master/src/satosa/wsgi.py#L12
>
>
>
>
>
>
>
> Il gio 1 ago 2019, 18:52 John Paraskevopoulos <io.paraskev at gmail.com>
> ha scritto:
>
>> Hello,
>>
>> My two cents regarding this discussion (I didn't have time to
>> check the discussion before the previous meeting so I thought I put
>> my two cents on email).
>>
>> I'm not sure I understand the use case where a shared config is
>> needed.
>>
>> I also believe that Singleton is an anti-pattern and should be
>> avoided unless absolutely needed (i.e mostly logging). Let's not
>> forget that this pattern was invented ~25 years ago where things
>> and resources were really different. In the case we're discussing
>> here, I think that the configuration is not a cross cutting
>> concern, and even if it is, we should try to fix this instead of
>> accepting it and trying to come up with a singleton. Making the
>> config globally available to everyone would hide the exact
>> dependencies of the components who call it (because the would
>> consume the whole config instead of some specific data) breaking
>> the separation of concerns concept. This would mean that we won't
>> be sure which config options we could change without breaking
>> things. Just because Django uses a single settings file doesn't
>> mean we should as well (firstly because Django is a different kind
>> of framework and secondly because Django doesn't mean perfect
>> design. An object should have all the data it needs, no more no
>> less and these should be clearly declared as dependency. This can
>> be achieved either as data parameter during initialization or
>> through a config Factory that will take care of the data processing
>> and return the needed information for each component (although this
>> in a way hides the config dependency it is still more preferable
>> than the singleton in my opinion).
>>
>> Currently from what I've seen and with my little experience on the
>> project's internals, there is some separation of concerns by having
>> different config files for different components. If we believe some
>> options from different components should be used by other
>> components as well we should first examine why this is happening
>> (maybe the use case scenario is wrong or maybe we need to redesign
>> things) and then see what needs fixing and how.
>>
>> On Thu, 1 Aug 2019, 00:26 Giuseppe De Marco,
>> <giuseppe.demarco at unical.it> wrote:
>>
>>> Hi All,
>>>
>>> this is a preliminar analisys about the strategy to obtain in MS
>>> constructors (__init__) the proxy configuration and also an
>>> abstract object where to store the shared state between MS (as we
>>> talked July the 9nth).
>>>
>>> In this place:
>>>
>>> https://github.com/IdentityPython/SATOSA/blob/master/src/satosa/base.py#L89
>>>
>>>
>>>
>>>
>>>
>>>
we could pass proxy config as argument or calling a global function that
>>> does the following before returning satosa_config
>>>
>>> config_file = os.environ.get("SATOSA_CONFIG", "proxy_conf.yaml")
>>> satosa_config = SATOSAConfig(config_file)
>>>
>>> This latter idea could introduce in addition also a refactor of
>>> this:
>>>
>>> https://github.com/IdentityPython/SATOSA/blob/master/src/satosa/wsgi.py#L12
>>>
>>>
>>>
>>>
>>>
>>>
>>>
moving it to a specialized function, importable everywhere, to get proxy
>>> configuration where needed. I think this is a better strategy to
>>> avoid passing configuration as argument in MS __init__.
>>>
>>> Regards
>>>
>>>
>>>
>>> Il giorno ven 12 lug 2019 alle ore 17:52 Rainer Hoerbe
>>> <rainer at hoerbe.at> ha scritto:
>>>
>>>> I am open to different solutions, with the preference to do it
>>>> consistently within SATOSA. So I am happy to accept decisions
>>>> from the group or our BDFL.
>>>>
>>>> However, I am a bit opinionated wrt passing the config via
>>>> function args. (1) By making an explicit argument an implicit
>>>> kwarg you would remove the possibility of static type checking.
>>>> And (2) in general, configuration is rather a cross-cutting
>>>> concern and should therefore not be part of a method signature.
>>>> Also, when tracing the code (-> import autologging), the config
>>>> data clutters the trace output.
>>>>
>>>> - Rainer
>>>>
>>>> Am 2019-07-12 um 15:41 schrieb Giuseppe De Marco <
>>>> giuseppe.demarco at unical.it>:
>>>>
>>>> Il giorno ven 12 lug 2019 alle ore 07:56 Rainer Hoerbe
>>>> <rainer at hoerbe.at> ha scritto:
>>>>
>>>>>
>>>>> right, with the exception that django hides some flexibility
>>>>> in configuring the settings file behind the import
>>>>> statement.
>>>>>
>>>>
>>>> settings = LazySettings() where LazySettings is at:
>>>> https://github.com/django/django/blob/master/django/conf/__init__.py#L42
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
If I'm not wrong you told me that in the django approach the filename
>>>> "settings" is a constant and this is a limit. This default
>>>> behaviour can be overriden:
>>>> os.environ.setdefault("DJANGO_SETTINGS_MODULE",
>>>> "django_peo.settings") so it just rely to a environment
>>>> variable called DJANGO_SETTINGS_MODULE.
>>>>
>>>>
>>>>>
>>>>>
>>>>> Not exactly. I have to 3 use cases:
>>>>>
>>>>> 1) obtain the satosa configuration in a microservice
>>>>> (currently only the ms configuration is available) 2) a
>>>>> potential refactoring that where modules would import
>>>>> configuration like in django, instead of passing the config
>>>>> as constructor argument. 3) pass the request state from
>>>>> request ms to the response ms
>>>>>
>>>>> The „singleton" pattern applies to 1 + 2, whereas I think the
>>>>> 3 is a separated concern. But it would be nice to have both
>>>>> types of state sharing as a defined infrastructure in satosa,
>>>>> not as a ms-specific hack.
>>>>>
>>>>>
>>>> Point 1) Got it! We could in addition let the "config" argument
>>>> to be handled in **kwargs. For example now we have:
>>>> RequestOrResponseMicroService.__init__(self, config, *args,
>>>> **kwargs):
>>>>
>>>> we could refactor in this way:
>>>> RequestOrResponseMicroService.__init__(self, *args, **kwargs):
>>>>
>>>> and handle the config with a overridable approach, like this
>>>> PoC: self.config = kwarg.get('config',
>>>> lazy_approach_to_get_global_config()) if theresn't an
>>>> overloaded config argument it will always get the global one.
>>>> This refactor will only impact on MS classes and the
>>>> contruction calls in the code to these, where the first
>>>> arguments "config" should be named, if needs to be overloaded
>>>> just in that MS runtime, or not -> for default behaviour.
>>>>
>>>> Point 2) Completely agree with you. I want to be honest, every
>>>> time I'll hear "Django" I'll say: Yes, it is, Yes, it does,
>>>> Yes, let's do it. I apologize and confess for it in advance! If
>>>> we agree we could spend some thoughs about an abstraction layer
>>>> to get and set configurations attributes runtime. We should
>>>> have a class to handle get/set for lazy_conf. In it,for every
>>>> plugins in the dictionary (key: name_of_plugin) for
>>>> INTERNAL_ATTRIBUTES, BACKEND_MODULES, FRONTEND_MODULES and
>>>> MICRO_SERVICES, we should have the same type with the same
>>>> methods... Or a multilevel nested dictionary for all. You'll
>>>> know which is better starting from to what we have at the
>>>> moment.
>>>>
>>>> Point 3) I'll listen to this topic the better I can to
>>>> undertand in the deep every related critical issues. From my
>>>> point of view, that ignorant one, if we would configure SATOSA
>>>> in a loadbalancer where the same user-agent session should be
>>>> shared by different workers/agents/servers we need to make some
>>>> shared storage persistence between these (Ivan told us about
>>>> that layer, starting from sqlalchemy that sounds quite good). I
>>>> confess in advance that I dislike cookies. From this stored
>>>> "session" we could get and set information runtime from
>>>> everywhere in the code.
>>>>
>>>> If these words were be code we would have just finished the
>>>> refactor! Too verbose!!
>>>>
>>>>
>>>>> - Rainer
>>>>>
>>>>
>>>> Thank you R
>>>>
>>>> ____________________ Dott. Giuseppe De Marco CENTRO ICT DI
>>>> ATENEO University of Calabria 87036 Rende (CS) - Italy Phone:
>>>> +39 0984 496961 e-mail: giuseppe.demarco at unical.it
>>>>
>>>>
>>>> ------------------------------------------------------------------------------------------------------------------
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
Il banner è generato automaticamente dal servizio di posta elettronica
>>>> dell'Università della Calabria <http://www.unical.it/5x1000>
>>>>
>>>>
>>>>
>>>
>>> -- ____________________ Dott. Giuseppe De Marco CENTRO ICT DI
>>> ATENEO University of Calabria 87036 Rende (CS) - Italy Phone: +39
>>> 0984 496961 e-mail: giuseppe.demarco at unical.it
>>> _______________________________________________ Satosa-dev
>>> mailing list Satosa-dev at lists.sunet.se
>>> https://lists.sunet.se/listinfo/satosa-dev
>>>
>>
>
We did not have time in the last call to discuss this:
There are use cases where we need to share state between a request and a response microservice. Now we have already 2 cases, so I suggest to define a common method to achieve this. The same approach could also be used to access the common config, e.g. if you need to know the proxy configuration (such as a backend entityid) in a microservice.
A simple mechanism would be to use a module-level variable as singleton:
=====================
shared_state.py
state = {}
———
plugins/microservices/a.py
from shared_state import state # import executes only once
…
state[’a’] = 'foo'
———
plugins/microservices/b.py
from shared_state import state
whatever(state['a‘])
=====================
I thing that for just passing request status to response microservices and passing config data around this should be good enough. There are several alternatives, like the Borg pattern, which I find harder to read.
- Rainer