Skip to content

Outbound TLS trust

Mount a combined CA bundle for outbound HTTPS from the Dreadnode API and bundled LiteLLM.

If Test connection fails with CERTIFICATE_VERIFY_FAILED for an internal model endpoint, configure an outbound CA bundle. Dreadnode mounts the bundle into the platform API and bundled LiteLLM, then sets standard HTTP, AWS, and gRPC trust variables to its path.

Outbound trust is chart configuration. Helm operators set it in a values overlay; Embedded Cluster operators use Config → Advanced Helm Values. Applying the configuration rolls the affected pods.

This is separate from the certificate Dreadnode presents at its own ingress. Use TLS certificates to configure that certificate, and Trust an internal certificate to make workstations trust it.

The bundle must contain your normal public roots as well as the private root and any intermediates needed for internal services. SSL_CERT_FILE replaces a client’s default CA file; a bundle that contains only the private root can make public providers stop working.

On a Debian or Ubuntu workstation, for example:

Terminal window
cp /etc/ssl/certs/ca-certificates.crt combined-ca-bundle.pem
cat organization-root.pem organization-intermediate.pem >> combined-ca-bundle.pem

Confirm that OpenSSL can parse every certificate:

Terminal window
openssl crl2pkcs7 -nocrl -certfile combined-ca-bundle.pem |
openssl pkcs7 -print_certs -noout

The bundle contains public certificate material only. Never add a private key or copy tls.key from the Dreadnode ingress Secret.

The chart references an existing Kubernetes Secret or ConfigMap. A Secret is the default below; use source: configMap when your organization manages public CA material that way.

Set NAMESPACE to the Helm release namespace, then create or update the Secret:

Terminal window
export NAMESPACE=dreadnode
kubectl -n "$NAMESPACE" create secret generic dreadnode-outbound-ca \
--from-file=ca.crt=/absolute/path/to/combined-ca-bundle.pem \
--dry-run=client -o yaml | kubectl apply -f -

Add the Secret reference to your values overlay and upgrade the release:

global:
caBundle:
enabled: true
source: secret
name: dreadnode-outbound-ca

The defaults read the ca.crt key and mount it at /etc/ssl/certs/dreadnode-ca-bundle.pem. Set key or mountPath in the same block when your object uses a different layout.

global.caBundle is intentionally a workload cascade, not a cluster-wide trust-store change:

ConnectionWhere to configure trustNotes
Bundled LiteLLM → model provider api_baseglobal.caBundle or dreadnode-litellm.caBundleLiteLLM’s standard HTTP, AWS, and gRPC paths receive the bundle variables. Provider-specific SDKs can require their own TLS option; verify with Test connection.
Dreadnode API → external HTTPS serviceglobal.caBundle or dreadnode-api.caBundleThe API receives the mounted bundle and standard variables for HTTP, AWS, and gRPC clients. See the exceptions below.
Dreadnode API → external LiteLLMAPI setting aboveThis secures the API-to-proxy connection. Configure provider trust on the external LiteLLM deployment itself.
Workstation → Dreadnode ingressClient TLS trustInstall the ingress issuer in the workstation trust store.
OpenSandbox runtime → Dreadnode, LiteLLM, or another HTTPS serviceSandbox runtime trustBake the CA into the custom runtime image; runtime pods do not inherit chart workload mounts.
Kubernetes node → private image registryNode or container-runtime configurationThe kubelet pulls images before a pod starts, so a pod volume cannot change registry trust.

The global bundle does not mean every API transport validates with that CA. caBundle cannot add private trust to Worlds HTTP backends or arbitrary webhook destinations today because those paths disable environment-derived HTTP trust. Use a destination certificate chained to a root already trusted by the container, or open a support request for a transport-specific option.

PostgreSQL’s DATABASE_USE_SSL mode encrypts the connection but does not validate its certificate, so do not treat caBundle as server-authentication support for PostgreSQL. The ClickHouse client is not passed an explicit CA file; validate that path in your environment before relying on the standard variables alone.

The sandbox server and controller normally use Kubernetes’ automatically mounted service-account CA to reach the Kubernetes API. They do not need the outbound bundle for that in-cluster path.

Leave global.caBundle.enabled false and configure a subchart when only one workload needs the private issuer. For a model provider reachable only from bundled LiteLLM:

dreadnode-litellm:
caBundle:
enabled: true
source: secret
name: dreadnode-outbound-ca

Use dreadnode-api.caBundle instead for an API-only destination. Non-empty local fields override the global name, source, key, mount path, and environment list.

For a provider that needs a separate file or trust-store format, create an operator-managed object:

Terminal window
kubectl -n "$NAMESPACE" create secret generic provider-trust \
--from-file=provider-ca.pem=/absolute/path/to/combined-provider-ca-bundle.pem \
--dry-run=client -o yaml | kubectl apply -f -

Then mount it with the LiteLLM escape hatch:

dreadnode-litellm:
extraVolumes:
- name: provider-trust
secret:
secretName: provider-trust
extraVolumeMounts:
- name: provider-trust
mountPath: /etc/dreadnode/provider-ca.pem
subPath: provider-ca.pem
readOnly: true
extraEnv:
- name: SSL_CERT_FILE
value: /etc/dreadnode/provider-ca.pem

SSL_CERT_FILE affects every standard LiteLLM HTTP provider, not only one deployment. The mounted file therefore must remain a combined public-and-private bundle. When a provider SDK documents a more specific TLS environment variable, set that variable through extraEnv instead.

Set the namespace and choose an enabled consumer: litellm for a model-provider connection, or api for an API-only destination. Embedded Cluster uses kotsadm.

Confirm that the chosen workload received the file:

Terminal window
export NAMESPACE=dreadnode # kotsadm for Embedded Cluster
export COMPONENT=litellm # or api
kubectl -n "$NAMESPACE" get pods \
-l app.kubernetes.io/component="$COMPONENT"
POD=$(kubectl -n "$NAMESPACE" get pod \
-l app.kubernetes.io/component="$COMPONENT" \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n "$NAMESPACE" exec "$POD" -- \
sh -c 'printf "%s\n" "$SSL_CERT_FILE"; test -r "$SSL_CERT_FILE"'

For LiteLLM, open Admin → Model Deployments, select the deployment that uses the private endpoint, and run Test connection. This exercises the actual provider path rather than only checking that the file exists. For the API, exercise the affected external service or integration.

To rotate the CA, rerun the Secret creation command with the updated combined bundle, then restart the consumers:

Terminal window
kubectl -n "$NAMESPACE" rollout restart deployment \
-l app.kubernetes.io/name=dreadnode-api
kubectl -n "$NAMESPACE" rollout restart deployment \
-l app.kubernetes.io/name=dreadnode-litellm

The bundle is mounted with subPath, so an updated Secret does not replace the file in a running pod. A restart is required, and it also clears TLS contexts cached by the application.