Skip to content

Commit

Permalink
refactor!: Configuration of signer moved into jwt finalizer (#1534)
Browse files Browse the repository at this point in the history
  • Loading branch information
dadrus committed Jun 18, 2024
1 parent 8236eba commit 4475745
Show file tree
Hide file tree
Showing 148 changed files with 3,209 additions and 2,129 deletions.
5 changes: 0 additions & 5 deletions charts/heimdall/templates/heimdall/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ data:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.signer }}
signer:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.mechanisms }}
mechanisms:
{{- toYaml . | nindent 6 }}
Expand Down
2 changes: 2 additions & 0 deletions cmd/validate/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func TestValidateConfig(t *testing.T) {
}

func TestRunValidateConfigCommand(t *testing.T) {
t.Parallel()

for _, tc := range []struct {
uc string
confFile string
Expand Down
22 changes: 21 additions & 1 deletion cmd/validate/ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ import (
"errors"
"os"

"github.com/go-jose/go-jose/v4"
"github.com/rs/zerolog"
"github.com/spf13/cobra"

"github.com/dadrus/heimdall/internal/config"
"github.com/dadrus/heimdall/internal/heimdall"
"github.com/dadrus/heimdall/internal/keyholder"
"github.com/dadrus/heimdall/internal/otel/metrics/certificate"
"github.com/dadrus/heimdall/internal/rules"
"github.com/dadrus/heimdall/internal/rules/mechanisms"
"github.com/dadrus/heimdall/internal/rules/provider/filesystem"
"github.com/dadrus/heimdall/internal/rules/rule"
"github.com/dadrus/heimdall/internal/watcher"
)

var errFunctionNotSupported = errors.New("function not supported")
Expand Down Expand Up @@ -82,7 +86,13 @@ func validateRuleSet(cmd *cobra.Command, args []string) error {

conf.Providers.FileSystem = map[string]any{"src": args[0]}

mFactory, err := mechanisms.NewFactory(conf, logger)
mFactory, err := mechanisms.NewMechanismFactory(
conf,
logger,
&watcher.NoopWatcher{},
&noopRegistry{},
&noopCertificateObserver{},
)
if err != nil {
return err
}
Expand All @@ -108,3 +118,13 @@ func (*noopRepository) FindRule(_ heimdall.Context) (rule.Rule, error) {
func (*noopRepository) AddRuleSet(_ string, _ []rule.Rule) error { return nil }
func (*noopRepository) UpdateRuleSet(_ string, _ []rule.Rule) error { return errFunctionNotSupported }
func (*noopRepository) DeleteRuleSet(_ string) error { return errFunctionNotSupported }

type noopRegistry struct{}

func (*noopRegistry) AddKeyHolder(_ keyholder.KeyHolder) {}
func (*noopRegistry) Keys() []jose.JSONWebKey { return nil }

type noopCertificateObserver struct{}

func (*noopCertificateObserver) Add(_ certificate.Supplier) {}
func (*noopCertificateObserver) Start() error { return errFunctionNotSupported }
69 changes: 63 additions & 6 deletions cmd/validate/ruleset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,48 @@ package validate

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"os"
"path/filepath"
"testing"

"github.com/drone/envsubst/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/dadrus/heimdall/internal/x/pkix/pemx"
"github.com/dadrus/heimdall/internal/x/stringx"
"github.com/dadrus/heimdall/internal/x/testsupport"
)

func TestValidateRuleset(t *testing.T) {
t.Parallel()
privKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
require.NoError(t, err)

pemBytes, err := pemx.BuildPEM(
pemx.WithECDSAPrivateKey(privKey, pemx.WithHeader("X-Key-ID", "key")),
)
require.NoError(t, err)

testDir := t.TempDir()
pemFile := filepath.Join(testDir, "keystore.pem")
configFile := filepath.Join(testDir, "test-config.yaml")

t.Setenv("TEST_KEYSTORE_FILE", pemFile)

err = os.WriteFile(pemFile, pemBytes, 0o600)
require.NoError(t, err)

raw, err := os.ReadFile("test_data/config.yaml")
require.NoError(t, err)

content, err := envsubst.EvalEnv(stringx.ToString(raw))
require.NoError(t, err)

err = os.WriteFile(configFile, []byte(content), 0o600)
require.NoError(t, err)

for _, tc := range []struct {
uc string
Expand All @@ -47,13 +78,13 @@ func TestValidateRuleset(t *testing.T) {
},
{
uc: "invalid rule set file",
confFile: "test_data/config.yaml",
confFile: configFile,
rulesFile: "doesnotexist.yaml",
expError: os.ErrNotExist,
},
{
uc: "everything is valid",
confFile: "test_data/config.yaml",
confFile: configFile,
rulesFile: "test_data/valid-ruleset.yaml",
},
} {
Expand Down Expand Up @@ -82,6 +113,32 @@ func TestValidateRuleset(t *testing.T) {
}

func TestRunValidateRulesCommand(t *testing.T) {
privKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
require.NoError(t, err)

pemBytes, err := pemx.BuildPEM(
pemx.WithECDSAPrivateKey(privKey, pemx.WithHeader("X-Key-ID", "key")),
)
require.NoError(t, err)

testDir := t.TempDir()
pemFile := filepath.Join(testDir, "keystore.pem")
configFile := filepath.Join(testDir, "test-config.yaml")

t.Setenv("TEST_KEYSTORE_FILE", pemFile)

err = os.WriteFile(pemFile, pemBytes, 0o600)
require.NoError(t, err)

raw, err := os.ReadFile("test_data/config.yaml")
require.NoError(t, err)

content, err := envsubst.EvalEnv(stringx.ToString(raw))
require.NoError(t, err)

err = os.WriteFile(configFile, []byte(content), 0o600)
require.NoError(t, err)

for _, tc := range []struct {
uc string
confFile string
Expand All @@ -95,20 +152,20 @@ func TestRunValidateRulesCommand(t *testing.T) {
},
{
uc: "everything is valid for decision mode usage",
confFile: "test_data/config.yaml",
confFile: configFile,
rulesFile: "test_data/valid-ruleset.yaml",
},
{
uc: "invalid for proxy usage",
proxyMode: true,
confFile: "test_data/config.yaml",
confFile: configFile,
rulesFile: "test_data/invalid-ruleset-for-proxy-usage.yaml",
expError: "requires forward_to",
},
{
uc: "everything is valid for proxy mode usage",
proxyMode: true,
confFile: "test_data/config.yaml",
confFile: configFile,
rulesFile: "test_data/valid-ruleset.yaml",
},
} {
Expand Down
5 changes: 5 additions & 0 deletions cmd/validate/test_data/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ mechanisms:
- id: jwt
type: jwt
config:
signer:
name: heimdall
key_id: "key"
key_store:
path: "${TEST_KEYSTORE_FILE}"
ttl: 5m
claims: |
{"user": {{ quote .Subject.ID }} }
Expand Down
13 changes: 6 additions & 7 deletions docs/content/docs/configuration/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,6 @@ profiling:
host: 0.0.0.0
port: 9000
signer:
name: foobar
key_store:
path: /opt/heimdall/keystore.pem
password: VeryInsecure!
key_id: foo
mechanisms:
authenticators:
- id: anonymous_authenticator
Expand Down Expand Up @@ -322,6 +315,12 @@ mechanisms:
- id: jwt
type: jwt
config:
signer:
name: foobar
key_store:
path: /opt/heimdall/keystore.pem
password: VeryInsecure!
key_id: foo
ttl: 5m
header:
name: Foo
Expand Down
144 changes: 108 additions & 36 deletions docs/content/docs/configuration/types.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -770,42 +770,6 @@ This matcher enables matching scopes using wildcards. It goes beyond the link:{{

This matcher can only be used by explicitly setting the `matching_strategy` to `wildcard` and defining the required patterns in the `values` property.

== Subject

This configuration type enables extraction of subject information from responses received by Heimdall from authentication services. Following properties are available.

* *`id`*: _string_ (mandatory)
+
A https://github.com/tidwall/gjson/blob/master/SYNTAX.md[GJSON Path] pointing to the id of the subject in the JSON object.

* *`attributes`*: _string_ (optional)
+
A https://github.com/tidwall/gjson/blob/master/SYNTAX.md[GJSON Path] pointing to the attributes of the subject in the JSON object. Defaults to `@this`.

.Extracting subject id from an https://tools.ietf.org/html/rfc7662[OAuth2 Introspection] endpoint response.
====
This example shows how to extract the subject id from an https://tools.ietf.org/html/rfc7662[OAuth2 Introspection] endpoint response and set the subject attributes to the entire response
[source, yaml]
----
id: sub
attributes: @this
----
Setting `attributes` was actually not required, as `@this` would be set by default anyway.
====

.Extracting subject id from an https://www.ory.sh/docs/kratos/[Ory Kratos] "whoami" endpoint response
====
This example shows how to extract the subject id from an https://www.ory.sh/docs/kratos/[Ory Kratos] "whoami" endpoint response and set the subject attributes to the entire response. `attributes` is not configured, so default is used.
[source, yaml]
----
id: identity.id
----
====

== Session Lifespan
This configuration type enables the configuration of session lifespans, used for session validation for those authenticators, which act on non-standard protocols. Following properties are available.
Expand Down Expand Up @@ -902,6 +866,114 @@ As you see, there is no need to define the time format as the times values appea
====

== Signer

When heimdall is used to issue signed objects, like JWTs, to enable upstream services to rely on authentic information, it acts as an issuer of such objects and requires corresponding configuration.

Following properties are supported:

* *`name`*: _string_ (optional)
+
The name used to specify the issuer. E.g. if a JWT is generated, this value is used to set the `iss` claim. If not set, the value `heimdall` is used.

* *`key_store`*: _link:{{< relref "/docs/configuration/types.adoc#_key_store" >}}[Key Store]_ (mandatory)
+
The key store containing the cryptographic material. At least one private key must be present.

* *`key_id`*: _string_ (optional)
+
If the `key_store` contains multiple keys, this property can be used to specify the key to use (see also link:{{< relref "/docs/configuration/types.adoc#_key_id_lookup" >}}[Key-Id Lookup]). If not specified, the first key is used. If specified, but there is no key for the given key id present, an error is raised and heimdall will refuse to start.

.Possible configuration
====
Imagine you have a PEM file located in `/opt/heimdall/keystore.pem` with the following contents:
[source, txt]
----
-----BEGIN EC PRIVATE KEY-----
X-Key-ID: foo
MIGkAgEBBDBRLr783dIM5NHJnDDMRVBiFSF56xqHle5lZk1ZCyyow9wKZGuF4EWK
jRBISBkE3NSgBwYFK4EEACKhZANiAAQ+oGUOJpVjntIWuanYxpXe6oN5tKhzLhBX
GP1SOXiLhnPNnN2uZu9KwOoBzoZhr/Fxw+sziXmzHJwjluz78VOlFKyopxTfmxRZ
0qq3f/KHWdDtVvmTfT0O/ux9mg6mCJw=
-----END EC PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIByjCCAVGgAwIBAgIBATAKBggqhkjOPQQDAzAuMQswCQYDVQQGEwJFVTENMAsG
A1UEChMEVGVzdDEQMA4GA1UEAxMHVGVzdCBDQTAeFw0yMjA4MTUwOTE3MTFaFw0y
MjA4MTUxMDE3MTFaMDAxCzAJBgNVBAYTAkVVMQ0wCwYDVQQKEwRUZXN0MRIwEAYD
VQQDEwlUZXN0IEVFIDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ+oGUOJpVjntIW
uanYxpXe6oN5tKhzLhBXGP1SOXiLhnPNnN2uZu9KwOoBzoZhr/Fxw+sziXmzHJwj
luz78VOlFKyopxTfmxRZ0qq3f/KHWdDtVvmTfT0O/ux9mg6mCJyjQTA/MA4GA1Ud
DwEB/wQEAwIHgDAMBgNVHQ4EBQQDYmFyMB8GA1UdIwQYMBaAFLO77bgPgZMKz11D
BVDUXvtNGeBnMAoGCCqGSM49BAMDA2cAMGQCMFRlx9Bq0MuSh5pDhDTqRq/MnxxD
W7qZg15AXoNnLrR60vV9gHjzkp1UkcU9viRIuAIwU0BjwDncp9z1seqKh+/eJV3f
xstQe2rzUEptWLIiPFoOBWZuw9wJ/Hunjik3a9T/
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIByjCCAVCgAwIBAgIBATAKBggqhkjOPQQDAzAuMQswCQYDVQQGEwJFVTENMAsG
A1UEChMEVGVzdDEQMA4GA1UEAxMHVGVzdCBDQTAeFw0yMjA4MTUwOTE3MTFaFw0y
MjA4MTYwOTE3MTFaMC4xCzAJBgNVBAYTAkVVMQ0wCwYDVQQKEwRUZXN0MRAwDgYD
VQQDEwdUZXN0IENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf96tstMNdNoNfYjl
bGY6BvBFTsl9E3hpPnta7SJn6BqIYz6KEohDJ+8DXwUMVb5Ytr/QkEikg966HCY3
A9TFBUdAs01TV8f2KoAPRQVrh+ccSLLJyACENfZ5VbGSQ0wso0IwQDAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUs7vtuA+BkwrPXUMF
UNRe+00Z4GcwCgYIKoZIzj0EAwMDaAAwZQIxAMPgE/Z+1Dcj+lH7jioE16Hig0HQ
FC4qBx1UU05H05Gs23ECB1hzD2qXikVpaNyuDgIwbogEu42wIwpDa5xdJIZcIhmz
DIuPvEscUDjU3C+1GPxmACcRMPv9QVUEcBAvZkfn
-----END CERTIFICATE-----
----
Then you can configure heimdall to use it like follows:
[source, yaml]
----
signer:
name: foobar
key_store:
path: /opt/heimdall/keystore.pem
key_id: foo
----
====

== Subject

This configuration type enables extraction of subject information from responses received by Heimdall from authentication services. Following properties are available.

* *`id`*: _string_ (mandatory)
+
A https://github.com/tidwall/gjson/blob/master/SYNTAX.md[GJSON Path] pointing to the id of the subject in the JSON object.

* *`attributes`*: _string_ (optional)
+
A https://github.com/tidwall/gjson/blob/master/SYNTAX.md[GJSON Path] pointing to the attributes of the subject in the JSON object. Defaults to `@this`.

.Extracting subject id from an https://tools.ietf.org/html/rfc7662[OAuth2 Introspection] endpoint response.
====
This example shows how to extract the subject id from an https://tools.ietf.org/html/rfc7662[OAuth2 Introspection] endpoint response and set the subject attributes to the entire response
[source, yaml]
----
id: sub
attributes: @this
----
Setting `attributes` was actually not required, as `@this` would be set by default anyway.
====

.Extracting subject id from an https://www.ory.sh/docs/kratos/[Ory Kratos] "whoami" endpoint response
====
This example shows how to extract the subject id from an https://www.ory.sh/docs/kratos/[Ory Kratos] "whoami" endpoint response and set the subject attributes to the entire response. `attributes` is not configured, so default is used.
[source, yaml]
----
id: identity.id
----
====


== TLS
Following are the supported TLS configuration properties:

Expand Down
Loading

0 comments on commit 4475745

Please sign in to comment.