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
87 changes: 80 additions & 7 deletions docs/modules/ROOT/partials/apis/camel-k-crds.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6413,6 +6413,37 @@ Defines a set of pods (namely those matching the label selector, relative to the
integration pod(s) should not be co-located with.


|===

[#_camel_apache_org_v1_trait_BaseTruststore]
=== BaseTruststore

*Appears on:*

* <<#_camel_apache_org_v1_trait_JVMTrait, JVMTrait>>

BaseTruststore represents an existing truststore to use as the base for adding certificates.

[cols="2,2a",options="header"]
|===
|Field
|Description

|`truststorePath` +
string
|


Path to the base truststore file.

|`passwordPath` +
string
|


Path to a file containing the password for the base truststore.


|===

[#_camel_apache_org_v1_trait_BuilderTrait]
Expand Down Expand Up @@ -6591,6 +6622,30 @@ When using `pod` strategy, annotation to use for the builder pod.
The list of manifest platforms to use to build a container image (default `linux/amd64`).


|===

[#_camel_apache_org_v1_trait_CACertConfig]
=== CACertConfig

*Appears on:*

* <<#_camel_apache_org_v1_trait_JVMTrait, JVMTrait>>

CACertConfig specifies a CA certificate to import into the truststore.

[cols="2,2a",options="header"]
|===
|Field
|Description

|`certPath` +
string
|


Path to the PEM-encoded CA certificate file to import.


|===

[#_camel_apache_org_v1_trait_CamelTrait]
Expand Down Expand Up @@ -7796,29 +7851,47 @@ 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` +
|`caCertificates` +
*xref:#_camel_apache_org_v1_trait_CACertConfig[[\]CACertConfig]*
|


A list of CA certificates to import into the truststore. Certificates must be mounted via the mount trait.

|`baseTruststore` +
*xref:#_camel_apache_org_v1_trait_BaseTruststore[BaseTruststore]*
|


Optional base truststore to use as the starting point for adding certificates.

|`truststorePasswordPath` +
string
|


Path to a PEM-encoded CA certificate file.
Example: "/etc/camel/conf.d/_secrets/my-ca/ca.crt"
Path to a file containing the password for the generated truststore. Required when using ca-certificates without base-truststore.

|`caCertMountPath` +
string
|


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

|`caCert` +
string
|


Deprecated: Use CACertificates instead. Path to a PEM-encoded CA certificate file.

|`caCertPassword` +
string
|


Required when caCert is set. Path to a file containing the truststore password.
Example: "/etc/camel/conf.d/_secrets/truststore-pass/password"
Deprecated: Use CACertificates instead. Path to a file containing the truststore password.


|===
Expand Down
82 changes: 67 additions & 15 deletions docs/modules/traits/pages/jvm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,29 @@ 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
| jvm.ca-certificates
| []github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait.CACertConfig
| A list of CA certificates to import into the truststore. Certificates must be mounted via the mount trait.

| jvm.base-truststore
| github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait.BaseTruststore
| Optional base truststore to use as the starting point for adding certificates.

| jvm.truststore-password-path
| string
| Path to a PEM-encoded CA certificate file.
Example: "/etc/camel/conf.d/_secrets/my-ca/ca.crt"
| Path to a file containing the password for the generated truststore. Required when using ca-certificates without base-truststore.

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

| jvm.ca-cert
| string
| Deprecated: Use CACertificates instead. Path to a PEM-encoded CA certificate file.

| jvm.ca-cert-password
| string
| Required when caCert is set. Path to a file containing the truststore password.
Example: "/etc/camel/conf.d/_secrets/truststore-pass/password"
| Deprecated: Use CACertificates instead. Path to a file containing the truststore password.

|===

Expand Down Expand Up @@ -112,7 +121,9 @@ $ kamel run --resource configmap:my-dep -t jvm.classpath=/etc/camel/resources/my

== 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.
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-certificates` option to add CA certificates to the JVM's truststore.

=== Single Certificate

First, create Kubernetes Secrets containing the CA certificate and truststore password:

Expand All @@ -129,25 +140,66 @@ Then mount the secrets using the mount trait and reference the file paths:
$ kamel run MyRoute.java \
-t mount.configs=secret:my-private-ca \
-t mount.configs=secret:my-truststore-pwd \
-t jvm.ca-cert=/etc/camel/conf.d/_secrets/my-private-ca/ca.crt \
-t jvm.ca-cert-password=/etc/camel/conf.d/_secrets/my-truststore-pwd/password
-t jvm.ca-certificates[0].cert-path=/etc/camel/conf.d/_secrets/my-private-ca/ca.crt \
-t jvm.truststore-password-path=/etc/camel/conf.d/_secrets/my-truststore-pwd/password
----

If your secret uses a different key name for the certificate:
=== Multiple Certificates

You can add multiple CA certificates to the truststore:

[source,console]
----
$ kamel run MyRoute.java \
-t mount.configs=secret:ca1 \
-t mount.configs=secret:ca2 \
-t mount.configs=secret:truststore-pwd \
-t jvm.ca-certificates[0].cert-path=/etc/camel/conf.d/_secrets/ca1/ca.crt \
-t jvm.ca-certificates[1].cert-path=/etc/camel/conf.d/_secrets/ca2/ca.crt \
-t jvm.truststore-password-path=/etc/camel/conf.d/_secrets/truststore-pwd/password
----

=== Using a Base Truststore (Preserving JDK Public CAs)

To preserve the JDK's default public CA certificates while adding your custom certificates, use the `base-truststore` option:

[source,console]
----
$ kamel run MyRoute.java \
-t mount.configs=secret:my-private-ca \
-t mount.configs=secret:my-truststore-pwd \
-t jvm.ca-cert=/etc/camel/conf.d/_secrets/my-private-ca/custom-ca.pem \
-t jvm.ca-cert-password=/etc/camel/conf.d/_secrets/my-truststore-pwd/password
-t mount.configs=secret:cacerts-pwd \
-t jvm.base-truststore.truststore-path=/opt/java/openjdk/lib/security/cacerts \
-t jvm.base-truststore.password-path=/etc/camel/conf.d/_secrets/cacerts-pwd/password \
-t jvm.ca-certificates[0].cert-path=/etc/camel/conf.d/_secrets/my-private-ca/ca.crt
----

NOTE: When using `base-truststore`, you can optionally provide `truststore-password-path` to set a different password for the output truststore. If not provided, the base truststore password is used.

=== Truststore Password Resolution

The truststore password is determined using this priority:

1. `truststore-password-path` (if explicitly provided)
2. `base-truststore.password-path` (if base-truststore is configured)
3. Validation error (password is required when using `ca-certificates`)

This will automatically:

1. Mount the secrets to the integration container (via mount trait)
2. Generate a JVM truststore using an init container
3. Configure the JVM to use the generated truststore via `-Djavax.net.ssl.trustStore`

NOTE: The `ca-cert-password` option is **required** when using `ca-cert`. Both values must be file paths to the mounted secrets.
=== Deprecated Syntax (Backward Compatible)

The legacy `ca-cert` and `ca-cert-password` options are still supported but deprecated:

[source,console]
----
$ kamel run MyRoute.java \
-t mount.configs=secret:my-private-ca \
-t mount.configs=secret:my-truststore-pwd \
-t jvm.ca-cert=/etc/camel/conf.d/_secrets/my-private-ca/ca.crt \
-t jvm.ca-cert-password=/etc/camel/conf.d/_secrets/my-truststore-pwd/password
----

NOTE: We recommend migrating to the new `ca-certificates` syntax for better multi-certificate support and explicit truststore password configuration.
110 changes: 98 additions & 12 deletions e2e/common/traits/jvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,36 +91,122 @@ func TestJVMTrait(t *testing.T) {
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
t.Run("JVM trait multiple CA certs", func(t *testing.T) {
// Test the new ca-certificates field with multiple certificates
cert1Pem, err := generateSelfSignedCert()
require.NoError(t, err)
cert2Pem, err := generateSelfSignedCert()
require.NoError(t, err)

// Create secrets with certificates
caCert1Data := make(map[string]string)
caCert1Data["ca.crt"] = string(cert1Pem)
err = CreatePlainTextSecret(t, ctx, ns, "test-ca-cert-1", caCert1Data)
require.NoError(t, err)

caCert2Data := make(map[string]string)
caCert2Data["ca.crt"] = string(cert2Pem)
err = CreatePlainTextSecret(t, ctx, ns, "test-ca-cert-2", caCert2Data)
require.NoError(t, err)

truststorePassData := make(map[string]string)
truststorePassData["password"] = "truststore-password"
err = CreatePlainTextSecret(t, ctx, ns, "truststore-pass-multi", truststorePassData)
require.NoError(t, err)

name := RandomizedSuffixName("multicacert")
g.Expect(KamelRun(t, ctx, ns,
"./files/Java.java",
"--name", name,
"-t", "mount.configs=secret:test-ca-cert-1",
"-t", "mount.configs=secret:test-ca-cert-2",
"-t", "mount.configs=secret:truststore-pass-multi",
"-t", "jvm.ca-certificates[0].cert-path=/etc/camel/conf.d/_secrets/test-ca-cert-1/ca.crt",
"-t", "jvm.ca-certificates[1].cert-path=/etc/camel/conf.d/_secrets/test-ca-cert-2/ca.crt",
"-t", "jvm.truststore-password-path=/etc/camel/conf.d/_secrets/truststore-pass-multi/password",
).Execute()).To(Succeed())

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

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"))
})

t.Run("JVM trait CA cert with base truststore", func(t *testing.T) {
// Test the new base-truststore field (replaces ca-cert-use-system-truststore)
certPem, err := generateSelfSignedCert()
require.NoError(t, err)

// Create secret with certificate
caCertData := make(map[string]string)
caCertData["ca.crt"] = string(certPem)
err = CreatePlainTextSecret(t, ctx, ns, "test-ca-sys", caCertData)
require.NoError(t, err)

// Create secret for base truststore password (JDK cacerts uses "changeit")
baseTsPassData := make(map[string]string)
baseTsPassData["password"] = "changeit"
err = CreatePlainTextSecret(t, ctx, ns, "base-ts-password", baseTsPassData)
require.NoError(t, err)

err = CreatePlainTextSecret(t, ctx, ns, "test-ca-cert", caCertData)
name := RandomizedSuffixName("syscacert")
g.Expect(KamelRun(t, ctx, ns,
"./files/Java.java",
"--name", name,
"-t", "mount.configs=secret:test-ca-sys",
"-t", "mount.configs=secret:base-ts-password",
"-t", "jvm.base-truststore.truststore-path=/opt/java/openjdk/lib/security/cacerts",
"-t", "jvm.base-truststore.password-path=/etc/camel/conf.d/_secrets/base-ts-password/password",
"-t", "jvm.ca-certificates[0].cert-path=/etc/camel/conf.d/_secrets/test-ca-sys/ca.crt",
).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))

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"))
})

t.Run("JVM trait CA cert single certificate", func(t *testing.T) {
// Test single certificate with the new ca-certificates field
certPem, err := generateSelfSignedCert()
require.NoError(t, err)

// Create secret with certificate
caCertData := make(map[string]string)
caCertData["ca.crt"] = string(certPem)
err = CreatePlainTextSecret(t, ctx, ns, "test-ca-single", caCertData)
require.NoError(t, err)

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

name := RandomizedSuffixName("cacert")
name := RandomizedSuffixName("singlecert")
g.Expect(KamelRun(t, ctx, ns,
"./files/Java.java",
"--name", name,
"-t", "mount.configs=secret:test-ca-cert",
"-t", "mount.configs=secret:test-ca-password",
"-t", "jvm.ca-cert=/etc/camel/conf.d/_secrets/test-ca-cert/ca.crt",
"-t", "jvm.ca-cert-password=/etc/camel/conf.d/_secrets/test-ca-password/password",
"-t", "mount.configs=secret:test-ca-single",
"-t", "mount.configs=secret:truststore-pass-single",
"-t", "jvm.ca-certificates[0].cert-path=/etc/camel/conf.d/_secrets/test-ca-single/ca.crt",
"-t", "jvm.truststore-password-path=/etc/camel/conf.d/_secrets/truststore-pass-single/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)
Expand Down
Loading
Loading