Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/modules/ROOT/partials/apis/camel-k-crds.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7678,6 +7678,22 @@ The Jar dependency which will run the application. Leave it empty for managed In

A list of JVM agents to download and execute with format `<agent-name>;<agent-url>[;<jvm-agent-options>]`.

|`caCert` +
string
|


The secret should contain PEM-encoded certificates.
Example: "secret:my-ca-certs" or "secret:my-ca-certs/custom-ca.crt"

|`caCertMountPath` +
string
|


The path where the generated truststore will be mounted
Default: "/etc/camel/conf.d/_truststore"


|===

Expand Down
59 changes: 59 additions & 0 deletions docs/modules/traits/pages/jvm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ Deprecated: no longer in use.
| []string
| A list of JVM agents to download and execute with format `<agent-name>;<agent-url>[;<jvm-agent-options>]`.

| jvm.ca-cert
| string
| The secret should contain PEM-encoded certificates.
Example: "secret:my-ca-certs" or "secret:my-ca-certs/custom-ca.crt"

| jvm.ca-cert-mount-path
| string
| The path where the generated truststore will be mounted
Default: "/etc/camel/conf.d/_truststore"

| jvm.ca-cert-password
| string
| **Required when ca-cert is set.** A secret reference containing the truststore password.
If the secret key is not specified, "password" is used as the default key.
Example: "secret:my-truststore-password" or "secret:my-truststore-password/mykey"

|===

// End of autogenerated code - DO NOT EDIT! (configuration)
Expand Down Expand Up @@ -94,3 +110,46 @@ You can use `jvm.classpath` configuration with dependencies available externally
kubectl create configmap my-dep --from-file=sample-1.0.jar
...
$ kamel run --resource configmap:my-dep -t jvm.classpath=/etc/camel/resources/my-dep/sample-1.0.jar MyApp.java

== Trusting Custom CA Certificates

When connecting to services that use TLS with certificates signed by a private CA (e.g., internal Elasticsearch, Kafka, or databases), you can use the `ca-cert` option to add the CA certificate to the JVM's truststore.

First, create a Kubernetes Secret containing the CA certificate:

[source,console]
----
kubectl create secret generic my-private-ca --from-file=ca.crt=/path/to/ca-certificate.pem
----

Next, create a Secret containing the truststore password:

[source,console]
----
kubectl create secret generic my-truststore-pwd --from-literal=password=mysecurepassword
----

Then reference both secrets when running the integration:

[source,console]
----
$ kamel run MyRoute.java -t jvm.ca-cert=secret:my-private-ca -t jvm.ca-cert-password=secret:my-truststore-pwd
----

If your certificate is stored under a different key in the secret:

[source,console]
----
$ kamel run MyRoute.java -t jvm.ca-cert=secret:my-private-ca/custom-ca.pem -t jvm.ca-cert-password=secret:my-truststore-pwd
----

This will automatically:

1. Mount the CA certificate secret
2. Generate a JVM truststore using an init container
3. Configure the JVM to use the generated truststore via `-Djavax.net.ssl.trustStore`
4. Inject the truststore password securely as an environment variable from your secret

NOTE: The `ca-cert-password` option is **required** when using `ca-cert`. The password is never exposed in command-line arguments - it is injected as an environment variable from the secret.


31 changes: 31 additions & 0 deletions e2e/common/traits/init_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,36 @@ func TestInitContainerTrait(t *testing.T) {
g.Eventually(IntegrationPodPhase(t, ctx, ns, name)).Should(Equal(corev1.PodRunning))
g.Eventually(IntegrationLogs(t, ctx, ns, name)).Should(ContainSubstring("helloSidecar10"))
})

t.Run("Init container reads mounted configs and resources", func(t *testing.T) {

configData := make(map[string]string)
configData["config.txt"] = "fromConfigMap"
g.Expect(CreatePlainTextConfigmap(t, ctx, ns, "init-cm", configData)).To(Succeed())

secretData := make(map[string]string)
secretData["secret.txt"] = "fromSecret"
g.Expect(CreatePlainTextSecret(t, ctx, ns, "init-secret", secretData)).To(Succeed())

resourceData := make(map[string]string)
resourceData["resource.txt"] = "fromResource"
g.Expect(CreatePlainTextConfigmap(t, ctx, ns, "init-resource", resourceData)).To(Succeed())

name := RandomizedSuffixName("init-all")
g.Expect(KamelRun(t, ctx, ns,
"files/init-container.yaml",
"-t", "mount.empty-dirs=common:/tmp",
"-t", "mount.configs=configmap:init-cm",
"-t", "mount.configs=secret:init-secret",
"-t", "mount.resources=configmap:init-resource",
"-t", "init-containers.init-tasks=init;alpine;/bin/sh -c \"cat /etc/camel/conf.d/_configmaps/init-cm/config.txt > /tmp/init && echo -n ' ' >> /tmp/init && cat /etc/camel/conf.d/_secrets/init-secret/secret.txt >> /tmp/init && echo -n ' ' >> /tmp/init && cat /etc/camel/resources.d/_configmaps/init-resource/resource.txt >> /tmp/init\"",
"--name", name,
).Execute()).To(Succeed())

g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("fromConfigMap"))
g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("fromSecret"))
g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("fromResource"))
})
})
}
72 changes: 72 additions & 0 deletions e2e/common/traits/jvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ package common

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"os"
"testing"
"time"

. "github.com/onsi/gomega"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -83,5 +90,70 @@ func TestJVMTrait(t *testing.T) {
g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("Hello World!"))
})

t.Run("JVM trait CA cert", func(t *testing.T) {
// Generate a valid self-signed certificate
certPem, err := generateSelfSignedCert()
require.NoError(t, err)

caCertData := make(map[string]string)
caCertData["ca.crt"] = string(certPem)

err = CreatePlainTextSecret(t, ctx, ns, "test-ca-cert", caCertData)
require.NoError(t, err)

passwordData := make(map[string]string)
passwordData["password"] = "test-password-123"
err = CreatePlainTextSecret(t, ctx, ns, "test-ca-password", passwordData)
require.NoError(t, err)

name := RandomizedSuffixName("cacert")
g.Expect(KamelRun(t, ctx, ns,
"./files/Java.java",
"--name", name,
"-t", "jvm.ca-cert=secret:test-ca-cert",
"-t", "jvm.ca-cert-password=secret:test-ca-password",
).Execute()).To(Succeed())

g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))

// Verify init container was added
pod := IntegrationPod(t, ctx, ns, name)()
g.Expect(pod).NotTo(BeNil())
initContainerNames := make([]string, 0)
for _, c := range pod.Spec.InitContainers {
initContainerNames = append(initContainerNames, c.Name)
}
g.Expect(initContainerNames).To(ContainElement("generate-truststore"))
})
})
}

// Helper to generate a self-signed certificate for testing
func generateSelfSignedCert() ([]byte, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}

template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Co"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24),

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, err
}

return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
}
Loading
Loading