Right now connectors implement certain types of behavior that could be reused. The most common example is filtering groups.
Consider creating a common middleware layer for connectors instead of implementing these features (and configuration for them) separately for each connector.
The most common example is filtering groups.
馃挴 It would be great to refactor that at least. If it fits a more generic pattern (as you described) all the better for it. 馃憤
related → #1790
+1 for this kind of pattern.
Maybe there is something to do/learn from ory oathkeeper: https://www.ory.sh/oathkeeper/docs/pipeline/mutator
(by the way... this kind of feature is awesome and we dream about it @renault :) : https://www.ory.sh/oathkeeper/docs/pipeline/mutator#hydrator)
We're going to want something like this too. If anyone has any suggestions about what designs they might prefer, I might have some time to work on this.
OK, so my assumption here is that the middleware layer will be passed the identity returned by a successful invocation of a Connector, and will return an (Identity, error) pair. Additionally, multiple middleware components can be stacked, in which case the identity output of one will be passed into the identity input of the next in the stack.
In addition, it looks like the Identity struct will need to be extended to enable custom claims, since middleware that adds custom claims is something people are likely to be interested in (in addition to being able to mutate group names and things). I know we are.
I'm fully agree with that.
As a use case : we operate an homemade extensions on Keycloak 馃様 in order to retrieve custom claims in order to enrich the user identity. This extension is a simple http call. I easily imagine a Dex "connector" that can do the same.
This kind of feature is for us very precious.
It would be very interesting to issue an HTTP request from dex and let OPA do the authorization for which claims to include, maybe even handover additional infos like HTTP request headers
馃挱 Just sharing a thought here: It could end up nicely if Dex embedded OPA, exposing an interface similar to what gatekeeper does.
A sketchy adaptation of that model to Dex would look like this:
constraints:
- kind: allowedGroups
match:
- connector: ldap # some connector ID
parameters:
groups:
- ops
- admin
spec:
crd:
spec:
names:
kind: allowedGroups
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
groups:
type: array
items: string
targets:
- rego: |
package allowedgroups
violation[{"msg": msg, "details": {"existing_groups": existing}}] {
existing := {group | input.review.identity.groups[_]}
allowed := {group | group := input.parameters.groups[_]}
overlap := allowed | provided # set intersection
count(overlap) == 0
msg := sprintf("must be in one of the allowed groups: %v", [allowed])
}
The most common constraint templates would be shipped with Dex, but it would also allow creating your own ones, and using them. The constraints would live in the config file, the constraint templates maybe in some other place.
The sketch doesn't include how you'd have the constraints eventually _alter_ the groups, or other claims, of a user, but that's something that could surely be done; but may have to be discussed, too.
This scheme has proven useful for gatekeeper, I believe, as it allows most usecases to be done from configuration without messing with Rego and OPA, but leaves an escape hatch for everyone who has special needs.
Furthermore, Rego policies can do HTTP requests, so this could be an extension point for using another system.
More information about the constraint framework can be found in https://github.com/open-policy-agent/frameworks/tree/master/constraint.
That sounds really interesting.
As far as I can tell, this is mostly useful for policy enforcement. As you also pointed out, it could alter groups and other claims, but I wonder if we should take a more generic approach and make OPA/rego an implementation detail providing policy enforcement.
@sagikazarmark Yeah it would also make for a decent middleware piece, sure. 馃槂
I note that this PR adds a CustomClaims to connector.Identity.
I think that changing connector.Identity in this way is a pre-requisite for external connectors (#1907) as well. If the connectors can't provide custom claims from upstream IDPs, then there will be less information for the middleware to work with.
Most helpful comment
OK, so my assumption here is that the middleware layer will be passed the identity returned by a successful invocation of a Connector, and will return an (Identity, error) pair. Additionally, multiple middleware components can be stacked, in which case the identity output of one will be passed into the identity input of the next in the stack.
In addition, it looks like the Identity struct will need to be extended to enable custom claims, since middleware that adds custom claims is something people are likely to be interested in (in addition to being able to mutate group names and things). I know we are.