wip: Multiple principals for a Subject #1317
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Related issue(s)
closes #921
Checklist
Background
In heimdall the term
Subject
is defined to represent the source of a request which is created upon successful authentication. This way, aSubject
may be any entity, such as a person, a service, or something else. Until now, aSubject
was represented by the following JSON schemawith
ID
being a unique identifier of the subject andAttributes
representing a dictionary of attributes related to the authenticated subject. These attributes could be for examples claims from a JWT used to authenticate the subject.This abstraction was enough for long time. But it has its drawbacks. In a real life a user wanting accessing an API, may use for example a laptop equipped with a client certificate from which the actual request is sent to the aforesaid API. It can be an IoT device, like e.g. a heating system, an end customer is using. It may even be an environment to which a user should authenticate first. In all these cases, we're actually talking about different and complementing authentication aspects related to the same request, but representing different entities (like a user and a device).
Reasoning, why not going for new authorizer types instead
It would simply lead to code bloating and potentially to a lot of duplication as for each authenticator, there would be a need for an authorizer doing the same thing.
Description
NOTE: This PR is in a very early stage. The text below describes the current ideas which might change during the implementation.
For the above said reasons, this PR introduces the following changes:
Subject
object to support multiplePrincipals
(the different entities mentioned above). That way theSubject
becomes an object holding the different authenticated principals, each having at least anID
andData
attributes.Subject
object, thePrinipal
objects are immutable, which means they cannot store data collected during the execution of the authentication & authorization pipeline.Data
property of thePrincipal
objects, a new object, namedOutputs
has been introduced.This way, a
Principal
is very similar to the oldSubject
and can be represented by the following JSON schema:The
Data
property can be the used JWT, the response from the introspection endpoint, an x509 certificate (in the future) and many more.The new
Subject
is then just an object in sense of a JSON object.With
<principal name ...>
being thePrincipal
entries representing theSubject
. Even it does not have anID
property any more, it has a newPrimary()
function, which returns the the primary principal - the principal, which has been created by an authentication step marked withprimary: true
, if multiple were specified in the pipeline, or if there was only one authenticator, the principal it creates.The new
Outputs
object can be represented using the following JSON schema:Changes in Detail
Mechanisms
subject
property of all authenticators have been renamed toprincipal
.anonymous
authenticator has been updated and does not allow any configuration anymore. That is, the principal id created by it is always "anonymous". It became a "special" authentictor. It cannot be combined with other authenticators any more. See the next section for details and reasoning.Authentication & Authorization Pipline
Important to note is that there are two types of fall backs for authenticators:
Before this PR, the first type has been addressed by just executing the next authenticator specified in the pipeline, and the second type by doing the same if the
allow_fallback_on_error
property of the failed authenticator was set totrue
.In sense of authentication stage configuration the requirement "verify a JWT if it is present and consider the request to be anonymous otherwise" (which basically means, the authentication step is optional) could be implemented as
and the requirement "verify the JWT using one JWKS endpoint if it comes from an idp1 and using another JWKS, if it comes from an idp2" could be implemented as
Since, as written above, all authenticators specified in the pipeline are always executed, the following authentication stage
would not describe a fallback any more and the question arises how to deal with fall backs now. This is addressed by introducing an new property
optional
of type boolean on the authentication stage step level. So, the above example can now be rewritten toThe usage of the anonymous authenticator becomes implicit. That is also the reason, why the updated authenticator of type
anonymous
does not allow any configuration any more more. TheID
of the principal it creates, has always the valueanonymous
.Since the
allow_fallback_on_error
property addresses the behavior of the pipeline and not the actual behavior of the authenticator, this property has been dropped from the authenticators, previously defining these. A new propertycontinue_on_error
of type boolean has been introduced on the step level. So, the requirement "verify the JWT using one JWKS endpoint if it comes from an idp1 and using another JWKS, if it comes from an idp2" from above would now be implemented asWith that in place, one can easily combine and chain any amount of authenticators, specify which are allowed to fail and which are optional to implement different requirements. That has however an implication: The combination of the
anonymous
authenticator with other authenticator types is not allowed. It can only be used standalone.Please note that the three new properties,
optional
,continue_on_error
, andprimary
introduced by this PR for authentication steps represent different requirements:optional
:true
means, that the execution of the authenticator is optional. If there is no authentication data, the authenticator should work on, ignore that and continue. If however, the authentication data is present and the authenticator fails validating it, the execution of the pipeline will be terminated with the corresponding error.continue_on_error
:true
goes beyond this and allows processing of the pipeline even if the authenticator fails validating the available authentication data. However, there must be at least one further authenticator in the given stage, otherwise the execution of the pipeline is terminated with an error as well.primary
:true
means, this particular authenticator creates the primary principal - the principal identifying the primary entity, which ID is also used for thesub
claim by the JWT finalizer. Indeed, if multiple authenticators are specified in the authentication stage, one of these must have theprimary
property set totrue
. It is an error otherwise and heimdall will refuse loading the corresponding rule, respectively rule set.There is one additional notable change: Each step in any stage has received an
id
, which is logged when the step is executed and has the following meaning:id
defines the name of the principal, created by the particular authenticator (the<principal name ...>
in the definition of theSubject
above). If not set, the principal name is set to theid
of the authenticator used in the step.Outputs
object and which then can be used in templates and expressions to access the corresponding data. If not set, theid
of the particular mechanism used in the step is used instead.All ids in the particular rule pipeline must be unique.
Templates & Expressions
There is still a
Subject
object available as before, but as written above with a different structure. And, as also written above, there is a newOutputs
object holding the results from the executed mechanisms. That affects how data can be accessed. Here are two examples highlighting the differences.Old
New
As can be seen, the main differences are:
Subject.ID
, you have now to useSubject.<some principal name>.ID
, respectivelySubject.Primary.ID
Subject.Attributes.iss
to access theiss
property from the authentication data used for the actual authentication, you have now to useSubject.<some principal name>.Data.iss
, orSubject.Primary.Data.iss
Subject.Attributes
anymore. Instead it is available on theOutputs
. So instead of usingSubject.Attributes.something
, you have now to useOutputs.something
Examples
With that in place, one can now chain multiple authentication mechanisms. Here examples for the implementation of the requirements described in #921.
Access to a staging environment. Only project members should be able to access the services (via e.g. a browser) to see and test the new deployed features. There is also an IAM in the staging environment itself which manages the "customers". So, the first IAM manages the access to the environment . The
X-Env-JWT
header certifies that the request has been routed through an authorized gateway (so access to the environment was legitimate). And the second IAM represents the actual users of the services deployed. Here, theAuthorization
header represents the user and describes its permissions through the scope claimwith the
jwt_env_authenticator
being configured to extract the token from theAuthorization
header and thejwt_user_authenticator
being configured to extract the token from theX-Env-JWT
header.This example indicates that the two mechanisms referenced in the above steps are pretty much the same. The only difference would be the configuration of the
jwt_source
, which extracts the token from different headers. this duplication is a tradeoff between simplicity in the rules and duplication in the config. Reconfiguration of thejwt_source
in a pipeline step was however never possible before. Opening it to the pipeline steps is possible, would however introduce a source for errors.Verification that the request came over a specific intermediary. Depending on the path the request took, the gateway issues an additional token, e.g. X-Caller-ID, which is then present in addition to the token in the Authorization header.
Open Topics
Maybe it would be better to organize everything in stages and make them explicit? That way, the old behavior would be preserved in sense of the semantic and also when the anonymous authenticator can be used. And it would be the different authentication stages which would each create a principal. That would mean less breaking changes, but
Current PR Status
In a very early stage. The changes implemented so far is the update of the
Subject
to let it be a map ofPrincipal
objects and have the code compilable.