Skip to content

Commit

Permalink
refactor!: values property for endpoint teplating must be configure…
Browse files Browse the repository at this point in the history
…d on the mechanism conf level (#746)
  • Loading branch information
dadrus authored Jun 29, 2023
1 parent edb69c5 commit 9809fe4
Show file tree
Hide file tree
Showing 15 changed files with 96 additions and 100 deletions.
4 changes: 2 additions & 2 deletions docs/content/docs/configuration/reference/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,14 @@ rules:
method: POST
headers:
foo-bar: "{{ .Subject.ID }}"
values:
some-key: some-value
auth:
type: api_key
config:
in: header
name: X-API-Key
value: super duper secret
values:
some-key: some-value
payload: "https://bla.bar"
expressions:
- expression: |
Expand Down
4 changes: 0 additions & 4 deletions docs/content/docs/configuration/reference/types.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,6 @@ Whether HTTP caching according to https://www.rfc-editor.org/rfc/rfc7234[RFC 723
+
NOTE: If the endpoint referenced by the URL does not provide any explicit expiration time, no heuristic freshness lifetime is calculated. Heimdall treats such responses as not cacheable.

* *`values`* _map of strings_ (optional)
+
A key value map, which is made accessible to the template rendering engine as link:{{< relref "../rules/pipeline_mechanisms/overview.adoc#_values" >}}[`Values`] object, e.g. to render parts of the URL.

.Endpoint configuration as string
====
[source, text]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,9 @@ To enable the usage of this authorizer, you have to set the `type` property to `

Configuration using the `config` property is mandatory. Following properties are available:

* *`endpoint`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_endpoint">}}[Endpoint]_ (mandatory, partially overridable)
* *`endpoint`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_endpoint">}}[Endpoint]_ (mandatory, not overridable)
+
The API endpoint of your authorization system. At least the `url` must be configured. This mechanism allows templating of the url and makes the link:{{< relref "overview.adoc#_subject" >}}[`Subject`] object, as well as the link:{{< relref "overview.adoc#_values" >}}[`Values`] (see also below) objects available to it. By default, this authorizer will use HTTP `POST` to send the rendered payload to this endpoint. You can override this behavior by configuring `method` as well. Depending on the API requirements of your authorization system, you might need to configure further properties, like headers, etc.
+
The only overridable property on the rule level is the `values` property, which allows rule specific configuration of the URL.

* *`payload`*: _string_ (optional, overridable)
+
Expand All @@ -134,6 +132,10 @@ Enables forwarding of any headers from the authorization endpoint response to th
+
Allows caching of the authorization endpoint responses. Defaults to 0s, which means no caching. The cache key is calculated from the entire configuration of the authorizer instance and the available information about the current subject.

* *`values`* _map of strings_ (optional, overridable)
+
A key value map, which is made accessible to the template rendering engine as link:{{< relref "overview.adoc#_values" >}}[`Values`] object, e.g. to render parts of the URL.

.Configuration of Remote authorizer to communicate with https://www.openpolicyagent.org/[Open Policy Agent] (OPA)
====
Here the remote authorizer is configured to communicate with OPA. Since OPA expects the query to be formatted as JSON, the corresponding `Content-Type` header is set. Since the responses are JSON objects as well, the `Accept` header is also provided. In addition, this examples uses the `basic_auth` auth type to authenticate against the endpoint.
Expand All @@ -153,11 +155,11 @@ config:
config:
user: ${OPA_USER}
password: ${OPA_PASSWORD}
values:
namespace: myapi/policy
policy: allow_write
payload: |
{ "input": { "user": {{ quote .Subject.ID }} } }
values:
namespace: myapi/policy
policy: allow_write
expressions:
- expression: |
Payload.result == true
Expand All @@ -183,9 +185,8 @@ A specific rule could then use this authorizer in the following ways:
- # other mechanisms
- authorizer: opa
config: # overriding with rule specifics
endpoint:
values:
policy: allow_read
values:
policy: allow_read
- # other mechanisms
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ To enable the usage of this contextualizer, you have to set the `type` property

Configuration using the `config` property is mandatory. Following properties are available:

* *`endpoint`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_endpoint">}}[Endpoint]_ (mandatory, partially overridable)
* *`endpoint`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_endpoint">}}[Endpoint]_ (mandatory, not overridable)
+
The API of the service providing additional attributes about the authenticated user. At least the `url` must be configured. This mechanism allows templating of the url and makes the link:{{< relref "overview.adoc#_subject" >}}[`Subject`] object, as well as the link:{{< relref "overview.adoc#_values" >}}[`Values`] (see also below) objects available to it. By default, this contextualizer will use HTTP `POST` to send the rendered payload to this endpoint. You can override this behavior by configuring `method` as well. Depending on the API requirements of the system, this contextualizer should communicate to, you might need to configure further properties, like headers, etc.
+
The only overridable property on the rule level is the `values` property, which allows rule specific configuration of the URL.

* *`forward_headers`*: _string array_ (optional, overridable)
+
Expand All @@ -49,6 +47,10 @@ Allows caching of the API responses. Defaults to 10 seconds. The cache key is ca
+
If set to `true`, allows the pipeline to continue with the execution of the next mechanisms. So the error, if thrown, is ignored. Defaults to `false`, which means the execution of the regular pipeline is stopped and the execution of the error pipeline is started.

* *`values`* _map of strings_ (optional, overridable)
+
A key value map, which is made accessible to the template rendering engine as link:{{< relref "overview.adoc#_values" >}}[`Values`] object, e.g. to render parts of the URL.

.Contextualizer configuration
====
Expand Down
5 changes: 2 additions & 3 deletions internal/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ type Endpoint struct {
AuthStrategy AuthenticationStrategy `mapstructure:"auth"`
Headers map[string]string `mapstructure:"headers"`
HTTPCacheEnabled *bool `mapstructure:"enable_http_cache"`
Values Values `mapstructure:"values"`
}

type Retry struct {
Expand Down Expand Up @@ -101,7 +100,7 @@ func (e Endpoint) CreateRequest(ctx context.Context, body io.Reader, rndr Render
method = e.Method
}

endpointURL, err := tpl.Render(e.URL, e.Values)
endpointURL, err := tpl.Render(e.URL)
if err != nil {
return nil, errorchain.NewWithMessage(heimdall.ErrInternal,
"failed to render URL for the endpoint").CausedBy(err)
Expand All @@ -128,7 +127,7 @@ func (e Endpoint) CreateRequest(ctx context.Context, body io.Reader, rndr Render
}

for headerName, valueTemplate := range e.Headers {
headerValue, err := tpl.Render(valueTemplate, e.Values)
headerValue, err := tpl.Render(valueTemplate)
if err != nil {
return nil, errorchain.NewWithMessagef(heimdall.ErrInternal,
"failed to render %s header value", headerName).CausedBy(err)
Expand Down
42 changes: 21 additions & 21 deletions internal/endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,23 @@ func TestEndpointCreateClient(t *testing.T) {
func TestEndpointCreateRequest(t *testing.T) {
t.Parallel()

renderer := RenderFunc(func(tpl string, values map[string]string) (string, error) {
tmpl, err := template.New("test").Parse(tpl)
if err != nil {
return "", err
}
renderer := func(values map[string]any) RenderFunc {
return func(tpl string) (string, error) {
tmpl, err := template.New("test").Parse(tpl)
if err != nil {
return "", err
}

var buf bytes.Buffer
var buf bytes.Buffer

err = tmpl.Execute(&buf, map[string]any{"Values": values})
if err != nil {
return "", err
}
err = tmpl.Execute(&buf, map[string]any{"Values": values})
if err != nil {
return "", err
}

return buf.String(), nil
})
return buf.String(), nil
}
}

for _, tc := range []struct {
uc string
Expand Down Expand Up @@ -311,10 +313,9 @@ func TestEndpointCreateRequest(t *testing.T) {
{
uc: "with templated url",
endpoint: Endpoint{
URL: "http://test.org/{{ .Values.key }}",
Values: map[string]string{"key": "foo"},
URL: "http://test.org/{{ .Values.key }}",
},
renderer: renderer,
renderer: renderer(map[string]any{"key": "foo"}),
assert: func(t *testing.T, request *http.Request, err error) {
t.Helper()

Expand All @@ -327,7 +328,7 @@ func TestEndpointCreateRequest(t *testing.T) {
endpoint: Endpoint{
URL: "http://test.org/{{ .Values.foo }",
},
renderer: renderer,
renderer: renderer(nil),
assert: func(t *testing.T, request *http.Request, err error) {
t.Helper()

Expand All @@ -339,14 +340,13 @@ func TestEndpointCreateRequest(t *testing.T) {
{
uc: "with templated header",
endpoint: Endpoint{
URL: "http://test.org",
Values: map[string]string{"key": "foo"},
URL: "http://test.org",
Headers: map[string]string{
"X-My-Header-1": "{{ .Values.key }}",
"X-My-Header-2": "bar",
},
},
renderer: renderer,
renderer: renderer(map[string]any{"key": "foo"}),
assert: func(t *testing.T, request *http.Request, err error) {
t.Helper()

Expand All @@ -363,7 +363,7 @@ func TestEndpointCreateRequest(t *testing.T) {
URL: "http://test.org",
Headers: map[string]string{"X-My-Header-1": "{{ .Values.key }"},
},
renderer: renderer,
renderer: renderer(nil),
assert: func(t *testing.T, request *http.Request, err error) {
t.Helper()

Expand All @@ -380,7 +380,7 @@ func TestEndpointCreateRequest(t *testing.T) {
}

// WHEN
req, err := tc.endpoint.CreateRequest(context.Background(), body, renderer)
req, err := tc.endpoint.CreateRequest(context.Background(), body, tc.renderer)

// THEN
tc.assert(t, req, err)
Expand Down
2 changes: 1 addition & 1 deletion internal/endpoint/noop_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package endpoint

type noopRenderer struct{}

func (noopRenderer) Render(template string, _ map[string]string) (string, error) {
func (noopRenderer) Render(template string) (string, error) {
return template, nil
}
8 changes: 4 additions & 4 deletions internal/endpoint/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
package endpoint

type Renderer interface {
Render(template string, values map[string]string) (string, error)
Render(template string) (string, error)
}

type RenderFunc func(template string, values map[string]string) (string, error)
type RenderFunc func(template string) (string, error)

func (f RenderFunc) Render(template string, values map[string]string) (string, error) {
return f(template, values)
func (f RenderFunc) Render(template string) (string, error) {
return f(template)
}
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,11 @@ func (a *genericAuthenticator) createRequest(ctx heimdall.Context, authData stri
WithErrorContext(a).CausedBy(err)
}

logger.Debug().Str("_payload", value).Msg("Request payload")

body = strings.NewReader(value)
}

req, err := a.e.CreateRequest(ctx.AppContext(), body,
endpoint.RenderFunc(func(value string, values map[string]string) (string, error) {
endpoint.RenderFunc(func(value string) (string, error) {
tpl, err := template.New(value)
if err != nil {
return "", errorchain.NewWithMessage(heimdall.ErrInternal, "failed to create template").
Expand Down
20 changes: 9 additions & 11 deletions internal/rules/mechanisms/authorizers/remote_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/dadrus/heimdall/internal/rules/mechanisms/contenttype"
"github.com/dadrus/heimdall/internal/rules/mechanisms/subject"
"github.com/dadrus/heimdall/internal/rules/mechanisms/template"
"github.com/dadrus/heimdall/internal/rules/mechanisms/values"
"github.com/dadrus/heimdall/internal/x"
"github.com/dadrus/heimdall/internal/x/errorchain"
"github.com/dadrus/heimdall/internal/x/stringx"
Expand Down Expand Up @@ -66,6 +67,7 @@ type remoteAuthorizer struct {
headersForUpstream []string
ttl time.Duration
celEnv *cel.Env
v values.Values
}

type authorizationInformation struct {
Expand Down Expand Up @@ -95,6 +97,7 @@ func newRemoteAuthorizer(id string, rawConfig map[string]any) (*remoteAuthorizer
Expressions []*cellib.Expression `mapstructure:"expressions"`
ResponseHeadersToForward []string `mapstructure:"forward_response_headers_to_upstream"`
CacheTTL time.Duration `mapstructure:"cache_ttl"`
Values values.Values `mapstructure:"values"`
}

var conf Config
Expand Down Expand Up @@ -138,6 +141,7 @@ func newRemoteAuthorizer(id string, rawConfig map[string]any) (*remoteAuthorizer
headersForUpstream: conf.ResponseHeadersToForward,
ttl: conf.CacheTTL,
celEnv: env,
v: conf.Values,
}, nil
}

Expand Down Expand Up @@ -197,16 +201,12 @@ func (a *remoteAuthorizer) WithConfig(rawConfig map[string]any) (Authorizer, err
return a, nil
}

type Endpoint struct {
Values endpoint.Values `mapstructure:"values"`
}

type Config struct {
Endpoint Endpoint `mapstructure:"endpoint"`
Payload template.Template `mapstructure:"payload"`
Expressions []*cellib.Expression `mapstructure:"expressions"`
ResponseHeadersToForward []string `mapstructure:"forward_response_headers_to_upstream"`
CacheTTL time.Duration `mapstructure:"cache_ttl"`
Values values.Values `mapstructure:"values"`
}

var conf Config
Expand All @@ -224,18 +224,16 @@ func (a *remoteAuthorizer) WithConfig(rawConfig map[string]any) (Authorizer, err
}
}

ept := a.e
ept.Values = ept.Values.Merge(conf.Endpoint.Values)

return &remoteAuthorizer{
id: a.id,
e: ept,
e: a.e,
payload: x.IfThenElse(conf.Payload != nil, conf.Payload, a.payload),
celEnv: a.celEnv,
expressions: x.IfThenElse(len(conf.Expressions) != 0, conf.Expressions, a.expressions),
headersForUpstream: x.IfThenElse(len(conf.ResponseHeadersToForward) != 0,
conf.ResponseHeadersToForward, a.headersForUpstream),
ttl: x.IfThenElse(conf.CacheTTL > 0, conf.CacheTTL, a.ttl),
v: a.v.Merge(conf.Values),
}, nil
}

Expand Down Expand Up @@ -303,7 +301,7 @@ func (a *remoteAuthorizer) createRequest(ctx heimdall.Context, sub *subject.Subj
}

req, err := a.e.CreateRequest(ctx.AppContext(), body,
endpoint.RenderFunc(func(tplString string, values map[string]string) (string, error) {
endpoint.RenderFunc(func(tplString string) (string, error) {
tpl, err := template.New(tplString)
if err != nil {
return "", errorchain.
Expand All @@ -314,7 +312,7 @@ func (a *remoteAuthorizer) createRequest(ctx heimdall.Context, sub *subject.Subj

return tpl.Render(map[string]any{
"Subject": sub,
"Values": values,
"Values": a.v,
})
}))
if err != nil {
Expand Down
Loading

0 comments on commit 9809fe4

Please sign in to comment.