Skip to content

Commit

Permalink
refactor!: unifier renamed to finalizer (#956)
Browse files Browse the repository at this point in the history
  • Loading branch information
dadrus authored Oct 6, 2023
1 parent f7058d4 commit d54e39d
Show file tree
Hide file tree
Showing 70 changed files with 1,406 additions and 1,668 deletions.
12 changes: 6 additions & 6 deletions DockerHub-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ rules:
authenticators:
- id: anonymous_authenticator
type: anonymous
unifiers:
finalizers:
- id: create_jwt
type: jwt

Expand All @@ -35,7 +35,7 @@ rules:
- POST
execute:
- authenticator: anonymous_authenticator
- unifier: create_jwt
- finalizer: create_jwt
```
Start heimdall:
Expand Down Expand Up @@ -104,7 +104,7 @@ rules:
type: deny
- id: allow_all_requests
type: allow
unifiers:
finalizers:
- id: create_jwt
type: jwt

Expand All @@ -115,7 +115,7 @@ rules:
execute:
- authenticator: anonymous_authenticator
- authorizer: deny_all_requests
- unifier: create_jwt
- finalizer: create_jwt

providers:
file_system:
Expand Down Expand Up @@ -210,8 +210,8 @@ X-Forwarded-For: 172.22.0.1
What did you actually do? ;)
* You've created a very simple configuration with a default rule, with preconfigured defaults. The used authenticator instructs heimdall to create an anonymous subject for every request on every URL for the HTTP methods GET and POST. The default authenticator rejects any request and the default unifier creates a JWT from the subject mentioned above.
* You've created a very simple rule, which reuses the default authenticator and unifier and configures an authorizer, which allows any request to pass through.
* You've created a very simple configuration with a default rule, with preconfigured defaults. The used authenticator instructs heimdall to create an anonymous subject for every request on every URL for the HTTP methods GET and POST. The default authenticator rejects any request and the default finalizer creates a JWT from the subject mentioned above.
* You've created a very simple rule, which reuses the default authenticator and finalizer and configures an authorizer, which allows any request to pass through.
* You've created and started a docker compose environment with heimdall operated in proxy mode and a "upstream" service, which responds with everything it receives.
* And sent an HTTP GET request to an imaginary `foobar` endpoint.
* Heimdall run the request through its pipeline and forwarded the enriched (`Authorization` header) request to the "upstream" service, which just returned all it has received to the caller.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ The above said decision and transformation process happens via rules, respective
That way, these rule sets can not only be managed centrally, but be deployed together with each particular upstream service as well without the need to restart or redeploy heimdall. Indeed, these rule sets are optional first class citizens of the upstream service and allow:

* implementation of secure defaults. If no rule matches the incoming request, a default decision and transformation, if configured, is applied. This is the reason for "optional first class citizens" above.
* configuration of as many authentication (e.g. OpenID Connect), authorization (e.g. via CEL expressions, or via OPA, or OpenFGA), contextualization (by e.g. communicating to some specific endpoint) and unification mechanisms, supported by heimdall, as required for the particular system. So, if your system requires integration with multiple authentication providers, or you want to migrate from one to another - it is just a matter of configuring them in heimdall.
* configuration of as many authentication (e.g. OpenID Connect), authorization (e.g. via CEL expressions, or via OPA, or OpenFGA), contextualization (by e.g. communicating to some specific endpoint) and finalization mechanisms (e.g. creation of a JWT out of the available subject information), supported by heimdall, as required for the particular system. So, if your system requires integration with multiple authentication providers, or you want to migrate from one to another - it is just a matter of configuring them in heimdall.
* reuse and combination of these mechanisms in as many rules, as required for the particular system.
* partial reconfiguration of a particular mechanism in a rule if required by the upstream service.
* authentication mechanism fallbacks
* implementation of different decision process schemes by combining e.g. authentication mechanisms with error handlers to drive authentication mechanism specific error handling strategies.
* execution of authorization and contextualization mechanisms in any order; that way, if the information about your subject, available from the authentication system, is not sufficient to make proper authorization decisions, you can let heimdall call other services to retrieve that additional information.
* conditional execution of authorization, contextualization and unification mechanisms is possible, e.g. if depending on the available information about the subject you would like heimdall to either block the request, or let the upstream return different representations of the requested resource.
* conditional execution of authorization, contextualization and finalization mechanisms is possible, e.g. if depending on the available information about the subject you would like heimdall to either block the request, or let the upstream return different representations of the requested resource.

## Beyond the functionality

Expand Down
6 changes: 3 additions & 3 deletions charts/heimdall/templates/demo/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ data:
type: deny
- id: allow_all_requests
type: allow
unifiers:
finalizers:
- id: create_jwt
type: jwt
- id: noop_unifier
- id: noop_finalizer
type: noop
default:
Expand All @@ -83,7 +83,7 @@ data:
execute:
- authenticator: anonymous_authenticator
- authorizer: deny_all_requests
- unifier: create_jwt
- finalizer: create_jwt
providers:
kubernetes: {}
Expand Down
2 changes: 1 addition & 1 deletion charts/heimdall/templates/demo/test-rule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ spec:
execute:
- authenticator: noop_authenticator
- authorizer: allow_all_requests
- unifier: noop_unifier
- finalizer: noop_finalizer
- id: anonymous-access
match:
url: http://<**>/anon/<**>
Expand Down
4 changes: 2 additions & 2 deletions cmd/validate/test_data/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ rules:
url: http://profile
headers:
foo: bar
unifiers:
finalizers:
- id: jwt
type: jwt
config:
Expand Down Expand Up @@ -182,7 +182,7 @@ rules:
- POST
execute:
- authenticator: anonymous_authenticator
- unifier: jwt
- finalizer: jwt
on_error:
- error_handler: authenticate_with_kratos

Expand Down
6 changes: 3 additions & 3 deletions cmd/validate/test_data/invalid-ruleset-for-proxy-usage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ rules:
- authenticator: hydra_authenticator
- contextualizer: subscription_contextualizer
- authorizer: allow_all_authorizer
- unifier: jwt
- finalizer: jwt
config:
claims: |
{"foo": "bar"}
- unifier: bla
- finalizer: bla
config:
headers:
foo-bar: bla
- unifier: blabla
- finalizer: blabla
# no on_error (reuses default)
6 changes: 3 additions & 3 deletions cmd/validate/test_data/valid-ruleset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ rules:
- authenticator: hydra_authenticator
- contextualizer: subscription_contextualizer
- authorizer: allow_all_authorizer
- unifier: jwt
- finalizer: jwt
config:
claims: |
{"foo": "bar"}
- unifier: bla
- finalizer: bla
config:
headers:
foo-bar: bla
- unifier: blabla
- finalizer: blabla
# no on_error (reuses default)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ menu:
parent: "Configuration"
---

Some link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/unifiers.adoc" >}}[Unifiers], which finalize the successful execution of the pipeline, can generated signed objects, like a JWT, to be forwarded to the upstream services. In such cases Heimdall acts as an issuer of such objects and needs at least corresponding key material.
Some link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/finalizers.adoc" >}}[Finalizers], which, as the name implies, finalize the successful execution of the pipeline, can generated signed objects, like a JWT, to be forwarded to the upstream services. In such cases Heimdall acts as an issuer of such objects and needs at least corresponding key material.

== Configuration

Expand Down
7 changes: 2 additions & 5 deletions docs/content/docs/configuration/reference/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,6 @@ tracing:
metrics:
enabled: true
host: 0.0.0.0
port: 9000
metrics_path: /metrics
profiling:
enabled: false
Expand Down Expand Up @@ -284,7 +281,7 @@ rules:
foo: bar
continue_pipeline_on_error: true
unifiers:
finalizers:
- id: jwt
type: jwt
config:
Expand Down Expand Up @@ -326,7 +323,7 @@ rules:
- POST
execute:
- authenticator: anonymous_authenticator
- unifier: jwt
- finalizer: jwt
on_error:
- error_handler: authenticate_with_kratos
Expand Down
26 changes: 13 additions & 13 deletions docs/content/docs/configuration/rules/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Defines where to forward the proxied request to. Used only when heimdall is oper
+
Host (and port) to be used for request forwarding. If no `rewrite` property (see below) is specified, all other parts, like scheme, path, etc. of the original url are preserved. E.g. if the original request is `\https://mydomain.com/api/v1/something?foo=bar&bar=baz` and the value of this property is set to `my-backend:8080`, the url used to forward the request to the upstream will be `\https://my-backend:8080/api/v1/something?foo=bar&bar=baz`
+
NOTE: The `Host` header is not preserved while forwarding the request. If you need it to be set to the value from the original request, make use of the link:{{< relref "pipeline_mechanisms/unifiers.adoc#_header" >}}[header unifier] in your `execute` pipeline and set it accordingly. The example below demonstrates that.
NOTE: The `Host` header is not preserved while forwarding the request. If you need it to be set to the value from the original request, make use of the link:{{< relref "pipeline_mechanisms/finalizers.adoc#_header" >}}[header finalizer] in your `execute` pipeline and set it accordingly. The example below demonstrates that.

** *`rewrite`*: _OriginalURLRewriter_ (optional)
+
Expand All @@ -105,7 +105,7 @@ If defined, heimdall will remove the specified query parameters from the origina

* *`execute`*: _link:{{< relref "#_regular_pipeline" >}}[Regular Pipeline]_ (mandatory)
+
Which mechanisms to use to authenticate, authorize, contextualize (enrich) and unify the subject and other information available in the request.
Which mechanisms to use to authenticate, authorize, contextualize (enrich) and finalize the pipeline.

* *`on_error`*: _link:{{< relref "#_error_handler_pipeline" >}}[Error Handler Pipeline]_ (optional)
+
Expand Down Expand Up @@ -133,13 +133,13 @@ execute:
- authenticator: foo
- authorizer: bar
- contextualizer: foo
- unifier: zab
- finalizer: zab
# the following one demonstrates how to preserve the
# Host header from the original request, while forwarding
# it to the upstream service
- unifier: preserve-host
- finalizer: preserve-host
# the config property can be omitted, if already configured
# in the header unifier mechanism ()
# in the header finalizer mechanism
config:
headers:
Host: '{{ .Request.Header("Host") }}'
Expand All @@ -150,17 +150,17 @@ on_error:

=== Regular Pipeline

As described in the link:{{< relref "/docs/getting_started/concepts.adoc" >}}[Concepts] section, heimdall's decision pipeline consists of multiple mechanisms - at least consisting of link:{{< relref "pipeline_mechanisms/authenticators.adoc" >}}[authenticators] and link:{{< relref "pipeline_mechanisms/unifiers.adoc" >}}[unifiers]. The definition of such a pipeline happens as a list of required mechanisms (previously link:{{< relref "pipeline_mechanisms/overview.adoc" >}}[configured]) with the corresponding ids in the following order:
As described in the link:{{< relref "/docs/getting_started/concepts.adoc" >}}[Concepts] section, heimdall's decision pipeline consists of multiple mechanisms - at least consisting of link:{{< relref "pipeline_mechanisms/authenticators.adoc" >}}[authenticators] and link:{{< relref "pipeline_mechanisms/finalizers.adoc" >}}[finalizers]. The definition of such a pipeline happens as a list of required mechanisms (previously link:{{< relref "pipeline_mechanisms/overview.adoc" >}}[configured]) with the corresponding IDs in the following order:

* List of link:{{< relref "pipeline_mechanisms/authenticators.adoc" >}}[authenticators] using `authenticator` as key, followed by the required authenticator `id`. Authenticators following the first defined in the list are used by heimdall as fallback. That is, if first authenticator fails due to missing authentication data, second is executed, etc. By default, fallback is not used if an authenticator fails due to validation errors of the given authentication data. E.g. if an authenticator fails to validate the signature of a JWT token, the next authenticator in the list will not be executed. Instead, the entire pipeline will fail and lead to the execution of the link:{{< relref "#_error_handler_pipeline" >}}[error handler pipeline]. This list is mandatory if no link:{{< relref "default.adoc" >}}[default rule] is configured.
+
NOTE: Some authenticators use the same sources to get subject authentication object from. E.g. the `jwt` and the `oauth2_introspection` authenticators can retrieve tokens from the same places in the request. If such authenticators are used in the same pipeline, you should configure the more specific ones before the more general ones to have working default fallbacks. To stay with the above example, the `jwt` authenticator is more specific compared to `oauth2_introspection`, as it will be only executed, if the token is in a JWT format. In contrast to this, the `oauth2_introspection` authenticator is more general and does not care about the token format, thus will feel responsible for the request as soon as it finds a bearer token. You can however also make use of the `allow_fallback_on_error` configuration property and set it to `true`. This will allow a fallback even if the verification of the credentials fail.
* List of link:({{< relref "pipeline_mechanisms/contextualizers.adoc" >}}[contextualizers] and link:({{< relref "pipeline_mechanisms/authorizers.adoc" >}}[authorizers] in any order (optional). Can also be mixed. As with authenticators, the list definition happens using either `contextualizer` or `authorizer` as key, followed by the required `id`. All mechanisms in this list are executed in the order, they are defined. If any of these fails, the entire pipeline fails, which leads to the execution of the link:{{< relref "#_error_handler_pipeline" >}}[error handler pipeline]. This list is optional.
* List of link:{{< relref "pipeline_mechanisms/unifiers.adoc" >}}[unifiers] using `unifiers` as key, followed by the required unifier `id`. All unifiers in this list are executed in the order, they are defined. If any of these fails, the entire pipeline fails, which leads to the execution of the link:{{< relref "#_error_handler_pipeline" >}}[error handler pipeline]. This list is mandatory if no link:{{< relref "default.adoc" >}}[default rule] is configured.
* List of link:{{< relref "pipeline_mechanisms/finalizers.adoc" >}}[finalizers] using `finalizers` as key, followed by the required finalizer `id`. All finalizers in this list are executed in the order they are defined. If any of these fail, the entire pipeline fails, which leads to the execution of the link:{{< relref "#_error_handler_pipeline" >}}[error handler pipeline]. This list is mandatory if no link:{{< relref "default.adoc" >}}[default rule] is configured.

In all cases, the used mechanism can be partially reconfigured if supported by the corresponding type. Configuration goes into the `config` properties. These reconfigurations are always local to the given rule. With other words, you can adjust your rule specific pipeline as you want without any side effects.

Execution of an `contextualizer`, `authorizer`, or `unifier` mechanisms can optionally happen conditionally by making use of a https://github.com/google/cel-spec[CEL] expression in an `if` clause, which has access to the link:{{< relref "pipeline_mechanisms/overview.adoc#_subject" >}}[`Subject`] and the link:{{< relref "pipeline_mechanisms/overview.adoc#_request" >}}[`Request`] objects. If the `if` clause is not present, the corresponding mechanism is always executed.
Execution of an `contextualizer`, `authorizer`, or `finalizer` mechanisms can optionally happen conditionally by making use of a https://github.com/google/cel-spec[CEL] expression in an `if` clause, which has access to the link:{{< relref "pipeline_mechanisms/overview.adoc#_subject" >}}[`Subject`] and the link:{{< relref "pipeline_mechanisms/overview.adoc#_request" >}}[`Request`] objects. If the `if` clause is not present, the corresponding mechanism is always executed.

.Complex pipeline
====
Expand Down Expand Up @@ -189,20 +189,20 @@ Execution of an `contextualizer`, `authorizer`, or `unifier` mechanisms can opti
// some expression logic deviating from the
// definition in the pipeline configuration.
# ... any further required authorizer or contextualizer
# list of unifiers
- unifier: foo
- unifier: bar
# list of finalizers
- finalizer: foo
- finalizer: bar
config:
headers:
- X-User-ID: {{ quote .ID }}
# ... any further required unifiers
# ... any further required finalizers
----
This example uses
* two authenticators, with authenticator named `bar` being the fallback for the authenticator named `foo`. This fallback authenticator is obviously of type link:{{< relref "pipeline_mechanisms/authenticators.adoc#_anonymous" >}}[anonymous] as it reconfigures the referenced prototype to use `anon` for subject id.
* multiple contextualizers and authorizers, with first contextualizer having its cache disabled (`cache_ttl` set to 0s) and the last authorizer being of type link:{{< relref "pipeline_mechanisms/authorizers.adoc#_local_cel" >}}[cel] as it reconfigures the referenced prototype to use a different authorization script.
* two unifiers, with the second one being obviously of type link:{{< relref "pipeline_mechanisms/unifiers.adoc#_header" >}}[header], as it defines a `X-User-ID` header set to the value of the subject id to be forwarded to the upstream service.
* two finalizers, with the second one being obviously of type link:{{< relref "pipeline_mechanisms/finalizers.adoc#_header" >}}[header], as it defines a `X-User-ID` header set to the value of the subject id to be forwarded to the upstream service.
* contextualizer `foo` is only executed if the authenticated subject is not anonymous.
* authorizer `foo` is only executed if the request method is HTTP POST.
====
Expand Down
Loading

0 comments on commit d54e39d

Please sign in to comment.