Security

To operate heimdall in a secure way, you should configure heimdall accordingly. Following sections address the corresponding areas.

HTTP Header Security Considerations

If trusted_proxies property is configured (see also the corresponding Decision and Proxy service configuration options) to let heimdall make use of different HTTP headers to build the URL for rule and HTTP method matching purposes, following logic apply:

  • The value for the used HTTP scheme is taken from the X-Forwarded-Proto header.

  • The value for the used HTTP host and port is taken from the X-Forwarded-Host header.

  • The value for the used HTTP path is taken from X-Forwarded-Uri header, which may also contain query parameters.

  • The value for the used HTTP method is taken from the X-Forwarded-Method header.

If the evaluation result for any of the above said steps is empty, the corresponding value is taken from the actual request to heimdall. E.g. if X-Forwarded-Method is set, the HTTP method used to communicate with heimdall is used for rule matching respectively evaluation purposes.

That means, if the client integrating with heimdall does not make use of the above said headers and does not drop them, a malicious actor could spoof them most probably leading to privileges escalation (depending on your rules). To avoid such situations, please adhere to the following practices:

  • If you can, try avoiding usage of trusted_proxies. Nothing can be spoofed then. However, you will lose the information about the used HTTP scheme, host and port and cannot rely on these in your rules.

  • Configure all headers and use those taking precedence. That is, always set X-Forwarded-Method, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Uri.

  • If you cannot influence, which headers are set by your system, you’re integrating with heimdall, let it drop unused ones. E.g. If the proxy forwarding the request to heimdall by default sets only X-Forwarded-Proto and X-Forwarded-Host, let it drop the X-Forwarded-Method and X-Forwarded-Uri headers.

The API Gateways & Proxies Guides follow these practices, respectively highlight where caution is required. So, you can find examples there.

Observability Information

Logs, metrics and profiling information is very valuable for operating heimdall. These are however also very valuable for any adversary. For this reason, the corresponding services, exposing such information are by default, if enabled, listening only on the loopback (127.0.0.1) interface. If you have to configure them to listen to other interfaces, e.g. because you operate heimdall in a container, make sure, you don’t expose them publicly.

TLS Trust Store

As documented in Concepts section, the execution of heimdall’s pipeline typically includes communication to other systems. The endpoints of the corresponding systems should be TLS protected. This is however actually out of scope for heimdall. What is in scope, is the verification of the used TLS server certificate if TLS is used. This happens by making use of the operating system-wide trust store, containing the certificates of Root and Intermediate CAs (trust anchors) shipped with the OS. That means, you should

  1. ensure this trust store contains the certificates of the Root CAs of your PKI hierarchy and

  2. ensure the endpoints, heimdall communicates with over TLS, provide not only their own certificates, but also the intermediate certificates and cross certificates not included within the OS trust store

Both is required to enable heimdall building the certificate chain for TLS server certificate verification purpose. If heimdall fails doing so, the connection will be dropped.

As written above, heimdall makes use of the OS wide trust store to build the certificate chain. The most common installation directory on a Linux system for that trust store is the /etc/ssl/certs directory. In addition to the separate root and intermediate CA certificates, it also contains a ca-certificates.crt file, containing all installed certificates as well. This file is used by heimdall for the aforesaid purpose.

heimdall container image is shipped without any certificates by intention to ensure you take care about the up-to-date status of the trust store. This way, if you use heimdall in a container, you have to mount the OS trust store into heimdall’s container to enable its usage.

E.g.

docker run -t -p 4456:4456 \
  -v $PWD:/heimdall/conf \
  -v /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro \
   dadrus/heimdall:latest serve decision \
  -c /heimdall/conf/heimdall.yaml

The verification of TLS server certificates is not the single configuration option. You should also ensure heimdall’s services, you’re using, are configured to be available via TLS as well. See TLS Configuration for all available options.

Signatures

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

Configuration

The configuration related to the issuance of signed objects can be done using the signer property, which resides on the top level of heimdall’s configuration and supports the following properties.

  • 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: Key Store (optional)

    The key store containing the cryptographic material. If configured, at least one private key and the corresponding certificate must be present. If not configured, heimdall generates an ECDSA P-384 key pair on start up and uses it then.

    You should always configure a valid key store for production use!
  • key_id: string (optional)

    If the key_store contains multiple keys, this property can be used to specify the key to use (see also 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.

Example 1. Possible configuration

Imagine you have a PEM file located in /opt/heimdall/keystore.pem with the following contents:

-----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:

signer:
  name: foobar
  key_store:
    path: /opt/heimdall/keystore.pem
  key_id: foo

Security Considerations

In a typical production scenario, there is a need for proper key and certificate management. This is supported by heimdall in the following way:

  • you can and should configure not only the private key for signature creation purposes, but also the corresponding certificate chain. This way your upstream services are able not only to verify the signatures of the signed objects for cryptographic validity, but also perform verification of the revocation status of used certificates and also their time validity. All of that is crucial for secure communication.

    The cryptographic material for the above said verification purposes is available via the JWKS endpoint for the upstream services.

  • you can configure multiple keys in heimdall’s key_store and specify the key_id of the key to use. The easiest way to let heimdall use the key id, you need, is to set X-Key-ID header in the PEM block of the corresponding private key (as also shown in the example above). Usage of key ids allows for seamless key rotation in setups which do not support or allow usage of secret management systems, respectively hot reloading of the corresponding updates by heimdall.

Secret Management & Rotation

When configuring heimdall, there are many places requiring secrets, like passwords, tokens, key material, etc. While you can directly configure these in heimdall’s config file, there is a huge chance for leaking them. Please reference the secrets in the config file via environment variables, or make use of external files where possible instead, and let the contents of these be managed by a secret management system.

Usage of external files can even allow you to rotate the configured secrets without the need to restart heimdall if desired. Watching for secrets rotation is however disabled by default, but can be enabled by setting the secrets_reload_enabled property to true on the top level of heimdall’s configuration.

As of today secret reloading is only supported for key stores and Redis cache backend credentials.

Verifying Heimdall Binaries and Container Images

Heimdall binaries and container images are signed using Cosign and the keyless signing feature.

Prerequisites

Container Image Signature Verification

The signatures are stored in a repository named dadrus/heimdall-signatures. To verify the container image using Cosign, execute the following command:

COSIGN_REPOSITORY=dadrus/heimdall-signatures \
cosign verify dadrus/heimdall:<tag> \
  --certificate-identity-regexp=https://github.com/dadrus/heimdall/.github/workflows/ci.yaml* \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com | jq
If you pull heimdall images from ghcr.io, reference the ghcr.io registry while specifying the repository names. So dadrus/heimdall-signatures becomes ghcr.io/dadrus/heimdall-signatures and dadrus/heimdall:<tag> becomes ghcr.io/dadrus/heimdall:<tag>.

In successful verification case, cosign will print similar output to the one shown below and exit with 0.

[
  {
    "critical": {
      "identity": {
        "docker-reference": "index.docker.io/dadrus/heimdall"
      },
      "image": {
        "docker-manifest-digest": "sha256:289b1a3eeeceeef08362a6fbcf4b95e726686d17998798e149c30b6974728eaf"
      },
      "type": "cosign container image signature"
    },
    "optional": {
      "1.3.6.1.4.1.57264.1.1": "https://token.actions.githubusercontent.com",
      "1.3.6.1.4.1.57264.1.2": "push",
      "1.3.6.1.4.1.57264.1.3": "04379639dc5f3fbfc260e883ee4938a35076d63e",
      "1.3.6.1.4.1.57264.1.4": "CI",
      "1.3.6.1.4.1.57264.1.5": "dadrus/heimdall",
      "1.3.6.1.4.1.57264.1.6": "refs/heads/main",
      "Bundle": {
        "SignedEntryTimestamp": "MEUCIFIvxs30zysroG6ItUNL+hfE3Cxn4GuiQe8d1u5N27OEAiEAqmzLrw80846U53nL/jtQ3U/2yx8Jqu8H75g6sihIcpg=",
        "Payload": {
          "body": "eyJhcGlWZXJzaW9uIjoi...xTMHRMUW89In19fX0=",
          "integratedTime": 1692727396,
          "logIndex": 32332529,
          "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
        }
      },
      "Issuer": "https://token.actions.githubusercontent.com",
      "Subject": "https://github.com/dadrus/heimdall/.github/workflows/ci.yaml@refs/heads/main",
      "githubWorkflowName": "CI",
      "githubWorkflowRef": "refs/heads/main",
      "githubWorkflowRepository": "dadrus/heimdall",
      "githubWorkflowSha": "04379639dc5f3fbfc260e883ee4938a35076d63e",
      "githubWorkflowTrigger": "push"
    }
  }
]

For released images, the Subject value ends with @refs/tags/<release version>.

Release Binary Signature Verification

The detached signatures, as well as certificates for all released archives are published together with the corresponding platform specific archive. The names of the signature files adhere to the <archive>-keyless.sig name pattern and the names of the certificate files adhere to the <archive>-keyless.pem name pattern, with <archive> being the archive for a platform specific build.

To verify the signature of the archive, hence its contents including the platform specific heimdall binary with Cosign execute the following command:

cosign verify-blob /path/to/the/downloaded/<archive> \
  --certificate-identity-regexp=https://github.com/dadrus/heimdall/.github/workflows/ci.yaml* \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --signature /path/to/the/downloaded/<archive>-keyless.sig \
  --certificate /path/to/the/downloaded/<archive>--keyless.pem

In successful verification case, cosign will print the following output and exit with 0.

Verified OK

Software Bill of Material (SBOM)

Heimdall is shipped with an SBOM in CyclonDX (json) format.

If you use a released binary of heimdall, the corresponding file is part of the platform specific archive. That way, if you verify the signature of the archive (see above), you do also get evidence about the validity of the SBOM.

If you use a container image, the same SBOM is attached to the image as attestation signed with Cosign. These attestations are stored in the dadrus/heimdall-sbom repository. To verify the attestation and retrieve the SBOM execute the following command once Cosign is installed:

COSIGN_REPOSITORY=dadrus/heimdall-sbom \
cosign verify-attestation dadrus/heimdall:<tag> \
  --certificate-identity-regexp=https://github.com/dadrus/heimdall/.github/workflows/ci.yaml* \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --type=cyclonedx
If you pull heimdall images from ghcr.io, reference the ghcr.io registry while specifying the repository names. So dadrus/heimdall-sbom becomes ghcr.io/dadrus/heimdall-sbom and dadrus/heimdall:<tag> becomes ghcr.io/dadrus/heimdall:<tag>.

In successful verification case, cosign will print similar output to the one shown below and exit with 0.

{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "eyJfdHlwZSI6Imh...LCJ2ZXJzaW9uIjoxfX0=",
  "signatures": [
    {
      "keyid": "",
      "sig": "MEQCICGdo9hmIUrBRzVQ23VS...6ToNGa5YrommZNCQ=="
    }
  ]
}

Here, payload is the base64 encoded attestation value embedding the SBOM.

As one-liner, you can verify the signature and extract the SBOM as follows:

COSIGN_REPOSITORY=dadrus/heimdall-sbom \
cosign verify-attestation dadrus/heimdall:<tag> \
  --certificate-identity-regexp=https://github.com/dadrus/heimdall/.github/workflows/ci.yaml* \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --type=cyclonedx | jq -r ".payload" | base64 -d | jq -r ".predicate" > heimdall.sbom.json

The result will be the heimdall.sbom.json SBOM document, which you can use with any SCA or monitoring tool of your choice, e.g. Dependency Track.

Last updated on Mar 12, 2024