Rule

Rules are the heart of heimdall. These allow execution of arbitrary logic, required by your upstream service. This section describes everything related to the configuration of a particular rule and how can these be combined to rule sets, which can then be loaded by a rule provider.

Rule Configuration

A single rule consists of the following properties:

  • id: string (mandatory)

    The unique identifier of a rule. It must be unique across all rules loaded by the same Rule Provider. To ensure this, it is recommended to let the id include the name of your upstream service, as well as its purpose. E.g. rule:my-service:public-api.

  • match: RuleMatcher (mandatory)

    Defines how to match a rule and supports the following properties:

    • url: string (mandatory)

      Glob or Regex pattern of the endpoints of your upstream service, which this rule should apply to. Query parameters are ignored.

    • strategy: string (optional)

      Which strategy to use for matching of the value, provided in the url property. Can be one of:

      • regex - to match url expressions by making use of regular expressions. Internally, heimdall makes use of Heimdall uses dlclark/regexp2 to implement this strategy. Head over to linked resource to get more insights about possible options.

        Example 1. Regular expressions patterns
        • https://mydomain.com/ matches https://mydomain.com/ and doesn’t match https://mydomain.com/foo or https://mydomain.com.

        • <https|http>://mydomain.com/<.*> matches https://mydomain.com/ and http://mydomain.com/foo. Doesn’t match https://other-domain.com/ or https://mydomain.com.

        • http://mydomain.com/<+> matches http://mydomain.com/123, but doesn’t match http://mydomain/abc.

        • http://mydomain.com/<(?!protected).*> matches http://mydomain.com/resource, but doesn’t match http://mydomain.com/protected.

      • glob - to match url expressions by making use of glob expressions. Internally, heimdall makes use of Heimdall uses gobwas/glob to implement this strategy. Head over to linked resource to get more insights about possible options.

        Example 2. Glob patterns
        • https://mydomain.com/<m?n> matches https://mydomain.com/man and does not match http://mydomain.com/foo.

        • https://mydomain.com/<{foo*,bar*}> matches https://mydomain.com/foo or https://mydomain.com/bar and doesn’t match https://mydomain.com/any.

  • methods: string array (optional)

    Which HTTP methods (GET, POST, PATCH, etc) are allowed for the matched URL. If not specified, every request to that URL will result in 405 Method Not Allowed response from heimdall. If all methods should be allowed, one can use a special ALL placeholder. If all, except some specific methods should be allowed, one can specify ALL and remove specific methods by adding the ! sign to the to be removed method. In that case you have to specify the value in braces. See also examples below.

    Example 3. Methods list which effectively expands to all HTTP methods
    methods:
      - ALL
    Example 4. Methods list consisting of all HTTP methods without TRACE and OPTIONS
    methods:
      - ALL
      - "!TRACE"
      - "!OPTIONS"
  • forward_to: RequestForwarder (mandatory in Proxy operation mode)

    Defines where to forward the proxied request to. Used only when heimdall is operated in the Proxy operation mode and supports the following properties:

    • host: string (mandatory)

      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

      As of today the Host header is preserved while forwarding the request. This will change in the future heimdall release. Instead of the Host header, heimdall will set the X-Forwarded-Host header with the same value. If you rely on that header to be preserved, add a unifier to your pipeline, which sets this value explicitly.
    • rewrite: OriginalURLRewriter (optional)

      Can be used to rewrite further parts of the original url before forwarding the request. If specified at least one of the following supported (middleware) properties must be specified:

      • scheme: string (optional)

        If defined, heimdall will use the specified value for the url scheme part while forwarding the request to the upstream.

      • strip_path_prefix: string (optional)

        If defined, heimdall will strip the specified prefix from the original url path. E.g. if the path of the original url is /api/v1/something and the value of this property is set to /api/v1, the request to the upstream will have the url path set to /something.

      • add_path_prefix: string (optional)

        This middleware is applied after the execution of the strip_path_prefix middleware described above. If defined, heimdall will add the specified path prefix to the path used to forward the request to the upstream service. E.g. if the path of the original url or the pass resulting after the application of the strip_path_prefix middleware is /something and the value of this property is set to /my-backend, the request to the upstream will have the url path set to /my-backend/something.

      • strip_query_parameters: string array (optional)

        If defined, heimdall will remove the specified query parameters from the original url before forwarding the request to the upstream service. E.g. if the query parameters part of the original url is foo=bar&bar=baz and the value of this property is set to ["foo"], the query part of the request to the upstream will be set to bar=baz

  • execute: Regular Pipeline (mandatory)

    Which mechanisms to use to authenticate, authorize, hydrate (enrich) and mutate the subject of the request.

  • on_error: Error Handler Pipeline (optional)

    Which error handler mechanisms to use if any of the mechanisms, defined in the execute property, fails. This property is optional only, if a default rule has been configured and contains an on_error definition.

Example 5. An example rule
id: rule:foo:bar
match:
  url: http://my-service.local/<**>
  strategy: glob
forward_to:
  host: backend-a:8080
  rewrite:
    scheme: http
    strip_path_prefix: /api/v1
methods:
  - GET
  - POST
execute:
  - authenticator: foo
  - authorizer: bar
  - contextualizer: foo
  - unifier: zab
on_error:
  - error_handler: foobar

Regular Pipeline

As described in the Concepts section, heimdall’s decision pipeline consists of multiple mechanisms - at least consisting of authenticators and unifiers. The definition of such a pipeline happens as a list of required mechanisms (previously configured) with the corresponding ids in the following order:

  • List of 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 error handler pipeline. This list is mandatory if no default rule is configured.

    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 contextualizers and 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 error handler pipeline. This list is optional.

  • List of 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 error handler pipeline. This list is mandatory if no 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 CEL expression in an if clause, which has access to the Subject and the Request objects. If the if clause is not present, the corresponding mechanism is always executed.

Example 6. Complex pipeline
# list of authenticators
- authenticator: foo
- authenticator: bar
  config:
    subject: anon
  # ... any further required authenticator
# list of authorizers and contextualizers in any order
- contextualizer: baz
  config:
    cache_ttl: 0s
- authorizer: zab
- contextualizer: foo
  if: Subject.ID != "anonymous"
- contextualizer: bar
- authorizer: foo
  if: Request.Method == "POST"
  config:
    expressions:
      - expression: |
          // some expression logic deviating from the
          // definition in the pipeline configuration.
  # ... any further required authorizer or contextualizer
# list of unifiers
- unifier: foo
- unifier: bar
  config:
    headers:
    - X-User-ID: {{ quote .ID }}
  # ... any further required unifiers

This example uses

  • two authenticators, with authenticator named bar being the fallback for the authenticator named foo. This fallback authenticator is obviously of type 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 cel as it reconfigures the referenced prototype to use a different authorization script.

  • two unifiers, with the second one being obviously of type 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.

Error Handler Pipeline

Compared to the Regular Pipeline, the error handler pipeline is pretty simple. It is also a list of mechanisms, but all referenced types are error handler types. Thus, each entry in this list must have error_handler as key, followed by the ìd of the required error handler, previously defined in Heimdall’s Pipeline Mechanisms configuration. Error handlers are always executed as fallbacks. So, if the condition of the first error handler does not match, second is selected, if its condition matches, it is executed, otherwise the next one is selected, etc. If none of the conditions of the defined error handlers match, the default error handler is executed.

As with the regular pipeline, partial reconfiguration of the used mechanisms is possible if supported by the corresponding type. The overrides are always local to the given rule as well.

Example 7. Two error handlers
- error_handler: foo
- error_handler: bar
  config:
    when:
      # rule specific conditions

This example uses two error handlers, named foo and bar. bar will only be selected by heimdall if foo 's error condition (defined in Heimdall’s Pipeline Mechanisms configuration) does not match. bar does also override the error condition as required by the given rule.

Rule Set

In principle, a rule set is just a list of rules with some additional meta information. Each RuleSet definition has the following attributes if not stated otherwise by a particular provider:

  • version: string (mandatory)

    The version schema of the RuleSet. The current version of heimdall supports only the version 1.

  • name: string (optional)

    The name of a rule set. Used only for logging purposes.

  • rules: Rule Configuration array (mandatory)

    List of the actual rules.

Example 8. Rule set with two rules
version: "1alpha2"
name: my-rule-set
rules:
- id: rule:1
  match:
    url: https://my-service1.local/<**>
  methods: [ "GET" ]
  execute:
    - authorizer: foobar
- id: rule:2
  match:
    url: https://my-service2.local/<**>
  methods: [ "GET" ]
  execute:
    - authorizer: barfoo

Last updated on Jul 20, 2023