Skip to content

Using keystores without a private key entry for tls results in panic #2833

@NiklasBeierl

Description

@NiklasBeierl

Preflight checklist

  • I agree to follow this project's Code of Conduct.
  • I have read and am following this repository's Contribution Guidelines."
  • I could not find a solution in the existing issues, docs, nor discussions.

Describe the bug

I was playing around with heimdall on kubernetes and wanted to use a certificate managed by cert-manager for the endpoints that heimdall exposes. (Management, Ingress & Webhook).

Note: If you have a similar setup, you might be interested in: #2834

Not thinking too much about it, I passed the tls.crt contained in the cert-manager secret to .tls.key_store. This however caused a go panic - see the logs below.

Of course, the primary problem here is that I did not pass a "complete" keystore to heimdall. But I suspect I won't be the last one to make that mistake, so it's probably better to have a more instructive error message for this case.

Note: Heimdall already does provide a more instructive error message if one passes a file that contains no pem-encoded certificate at all.

heimdall-1       | 2025-10-24T09:49:09Z ERR OnStart hook failed error="internal error: Could not create listener for Management service: configuration error: key store entry is not suitable for TLS: no certificate present" _caller=go.uber.org/fx.(*lifecycleHookAnnotation).buildHookInstaller.func1 _functionName=go.uber.org/fx.(*lifecycleHookAnnotation).buildHookInstaller.func1.1()
heimdall-1       | 2025-10-24T09:49:09Z ERR Start failed, rolling back error="internal error: Could not create listener for Management service: configuration error: key store entry is not suitable for TLS: no certificate present"

How can the bug be reproduced

Reproduction (certs included):

https://github.com/NiklasBeierl/heimdall/tree/cert-file-panic

docker compose -f docker-compose.yaml -f docker-compose-tls-error.yaml up

Relevant log output

panic: runtime error: index out of range [0] with length 0

goroutine 66 [running]:
github.com/dadrus/heimdall/internal/x/tlsx.(*keyStore).load(0xc001222000)
  github.com/dadrus/heimdall/internal/x/tlsx/key_store.go:90 +0xa31
github.com/dadrus/heimdall/internal/x/tlsx.newTLSKeyStore({0xc000b3efa8, 0x14}, {0x0, 0x0}, {0x0, 0x0})
  github.com/dadrus/heimdall/internal/x/tlsx/key_store.go:54 +0xa6
github.com/dadrus/heimdall/internal/x/tlsx.ToTLSConfig(0xc000b9fb30, {0xc000e3f2c0, 0x3, 0x7f4ae7b24d58?})
  github.com/dadrus/heimdall/internal/x/tlsx/tls.go:38 +0xe5
github.com/dadrus/heimdall/internal/handler/listener.newTLSListener({0x37704d1?, 0x3e26f60?}, 0xc000dd0b60?, {0x3e15890, 0xc00121c010}, {0x3de1860?, 0x5df6440?}, {0x0?, 0x0?})
  github.com/dadrus/heimdall/internal/handler/listener/listener.go:126 +0xd5
github.com/dadrus/heimdall/internal/handler/listener.New({0x3e26f60?, 0xc000dd0b60?}, {0x37704d1, 0xa}, {0xc000dc4468?, 0x47edc5?}, 0xc000b9fb30, {0x3de1860, 0x5df6440}, {0x0, ...})
  github.com/dadrus/heimdall/internal/handler/listener/listener.go:113 +0x134
github.com/dadrus/heimdall/internal/handler/fxlcm.(*LifecycleManager).Start(0xc000d75ad0, {0x3e26f60, 0xc000dd0b60})
  github.com/dadrus/heimdall/internal/handler/fxlcm/lifecycle_manager.go:53 +0xa5
github.com/dadrus/heimdall/internal/handler/management.init.func1({0x3e26f60?, 0xc000dd0b60?}, 0x0?)
  github.com/dadrus/heimdall/internal/handler/management/module.go:31 +0x2c
reflect.Value.call({0x3092dc0?, 0x3908a08?, 0x0?}, {0x375e4dd, 0x4}, {0xc000dde6f0, 0x2, 0xc000f86410?})
  reflect/value.go:581 +0xcc6
reflect.Value.Call({0x3092dc0?, 0x3908a08?, 0x7f4aa0b32cc0?}, {0xc000dde6f0?, 0x50?, 0xc0000d1008?})
  reflect/value.go:365 +0xb9
go.uber.org/fx.(*lifecycleHookAnnotation).buildHookInstaller.func1.1({0x3e26f60?, 0xc000dd0b60})
  go.uber.org/fx@v1.24.0/annotated.go:842 +0x2c5
go.uber.org/fx/internal/lifecycle.(*Lifecycle).runStartHook(0xc000000540, {0x3e26f60, 0xc000dd0b60}, {0xc000dd0230, 0x0, {0x0, 0x0}, {0x0, 0x0}, {{0xc000dd43c0, ...}, ...}})
  go.uber.org/fx@v1.24.0/internal/lifecycle/lifecycle.go:256 +0x1f2
go.uber.org/fx/internal/lifecycle.(*Lifecycle).Start(0xc000000540, {0x3e26f60, 0xc000dd0b60})
  go.uber.org/fx@v1.24.0/internal/lifecycle/lifecycle.go:216 +0x468
go.uber.org/fx.(*App).start-fm.(*App).start.func1({0x3e26f60?, 0xc000dd0b60?})
  go.uber.org/fx@v1.24.0/app.go:702 +0x31
go.uber.org/fx.(*App).withRollback(0xc000656dc0, {0x3e26f60, 0xc000dd0b60}, 0x0?)
  go.uber.org/fx@v1.24.0/app.go:684 +0x32
go.uber.org/fx.(*App).start(...)
  go.uber.org/fx@v1.24.0/app.go:701
go.uber.org/fx.withTimeout.func1()
  go.uber.org/fx@v1.24.0/app.go:801 +0x6b
created by go.uber.org/fx.withTimeout in goroutine 1
  go.uber.org/fx@v1.24.0/app.go:789 +0xc5

Relevant configuration

serve:
  tls:
    key_store:
      path: /etc/secrets/tls.crt

management:
  tls:
    key_store:
      path: /etc/secrets/tls.crt

providers:
  file_system:
    src: /etc/heimdall/rules.yaml
    watch: true
# Probably requires kubernetes to test
#  kubernetes:
#    tls:
#      key_store:
#        path: /etc/secrets/heimdall-cert/tls.crt

Version

heimdall version v0.17.3

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Docker Compose

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions