This is an automated email from the ASF dual-hosted git repository.
fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 5bc6aa899828 CAMEL-23280: Add SSL/TLS configuration support to
camel-openai component
5bc6aa899828 is described below
commit 5bc6aa899828471e3b1bb993a97320c9b516a2a6
Author: Marco Carletti <[email protected]>
AuthorDate: Thu Apr 2 08:08:17 2026 +0200
CAMEL-23280: Add SSL/TLS configuration support to camel-openai component
Add dedicated SSL properties (truststore, keystore, hostname verification,
protocol, algorithms) to OpenAIConfiguration, implement SSL context creation
in OpenAIEndpoint, and update documentation with usage examples.
Make OpenAIComponent implement SSLContextParametersAware so that global
SSLContextParameters can be used. When SSLContextParameters is set
(endpoint-level or global), it takes precedence over individual SSL
properties, following the same pattern as camel-kafka.
Includes comprehensive tests: configuration unit tests, TLS mock tests
with JKS/PKCS12 truststores, mTLS mock tests covering mutual
authentication, and SSLContextParameters fallback/precedence tests.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../apache/camel/catalog/components/openai.json | 17 +-
.../openai/OpenAIComponentConfigurer.java | 6 +
.../component/openai/OpenAIEndpointConfigurer.java | 72 ++++++
.../component/openai/OpenAIEndpointUriFactory.java | 19 +-
.../org/apache/camel/component/openai/openai.json | 17 +-
.../src/main/docs/openai-component.adoc | 123 ++++++++++
.../camel/component/openai/OpenAIComponent.java | 21 +-
.../component/openai/OpenAIConfiguration.java | 151 ++++++++++++
.../camel/component/openai/OpenAIEndpoint.java | 105 ++++++++
.../camel/component/openai/OpenAIMtlsMockTest.java | 269 +++++++++++++++++++++
.../openai/OpenAISslConfigurationTest.java | 268 ++++++++++++++++++++
.../openai/OpenAISslContextParametersTest.java | 265 ++++++++++++++++++++
.../camel/component/openai/OpenAISslMockTest.java | 238 ++++++++++++++++++
.../apache/camel/component/openai/ssl/README.md | 67 +++++
.../openai/ssl/test-keystore-diffpass.jks | Bin 0 -> 2246 bytes
.../camel/component/openai/ssl/test-keystore.jks | Bin 0 -> 2740 bytes
.../camel/component/openai/ssl/test-keystore.p12 | Bin 0 -> 2740 bytes
.../openai/ssl/test-truststore-diffpass.jks | Bin 0 -> 958 bytes
.../camel/component/openai/ssl/test-truststore.jks | Bin 0 -> 1270 bytes
.../camel/component/openai/ssl/test-truststore.p12 | Bin 0 -> 1270 bytes
.../dsl/OpenaiComponentBuilderFactory.java | 18 ++
.../endpoint/dsl/OpenAIEndpointBuilderFactory.java | 200 +++++++++++++++
22 files changed, 1849 insertions(+), 7 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
index e9366504a874..feb4bfac903d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
@@ -29,7 +29,8 @@
"embeddingModel": { "index": 2, "kind": "property", "displayName":
"Embedding Model", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "description": "Default model for embeddings endpoints"
},
"lazyStartProducer": { "index": 3, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
"model": { "index": 4, "kind": "property", "displayName": "Model",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Default model for chat completion endpoints" },
- "autowiredEnabled": { "index": 5, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "autowiredEnabled": { "index": 5, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "useGlobalSslContextParameters": { "index": 6, "kind": "property",
"displayName": "Use Global Ssl Context Parameters", "group": "security",
"label": "security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Enable usage of global SSL context
parameters" }
},
"headers": {
"CamelOpenAIUserMessage": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The user message to send to the OpenAI chat completion
API", "constantName":
"org.apache.camel.component.openai.OpenAIConstants#USER_MESSAGE" },
@@ -89,6 +90,18 @@
"topP": { "index": 24, "kind": "parameter", "displayName": "Top P",
"group": "producer", "label": "", "required": false, "type": "number",
"javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "Top P for response generation (0.0 to 1.0)" },
"userMessage": { "index": 25, "kind": "parameter", "displayName": "User
Message", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "Default user message
text to use when no prompt is provided" },
"lazyStartProducer": { "index": 26, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
- "oauthProfile": { "index": 27, "kind": "parameter", "displayName": "Oauth
Profile", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "OAuth profile name for
obtaining an access token via the OAuth 2.0 Client Cred [...]
+ "oauthProfile": { "index": 27, "kind": "parameter", "displayName": "Oauth
Profile", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "OAuth profile name for
obtaining an access token via the OAuth 2.0 Client Cred [...]
+ "sslContextParameters": { "index": 28, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "SSLContextParameters to
use [...]
+ "sslEndpointAlgorithm": { "index": 29, "kind": "parameter", "displayName":
"Ssl Endpoint Algorithm", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"https", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The endpoint identification algorith [...]
+ "sslKeymanagerAlgorithm": { "index": 30, "kind": "parameter",
"displayName": "Ssl Keymanager Algorithm", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "defaultValue": "SunX509", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The algorithm used by the key [...]
+ "sslKeyPassword": { "index": 31, "kind": "parameter", "displayName": "Ssl
Key Password", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "The password of the
private key in the key store file" },
+ "sslKeystoreLocation": { "index": 32, "kind": "parameter", "displayName":
"Ssl Keystore Location", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "The location of the key
store file. This is optional and can be [...]
+ "sslKeystorePassword": { "index": 33, "kind": "parameter", "displayName":
"Ssl Keystore Password", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "The store password for
the key store file" },
+ "sslKeystoreType": { "index": 34, "kind": "parameter", "displayName": "Ssl
Keystore Type", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"JKS", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The file format of the key store file" },
+ "sslProtocol": { "index": 35, "kind": "parameter", "displayName": "Ssl
Protocol", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"TLSv1.3", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The SSL protocol used to generate the
SSLContext" },
+ "sslTrustmanagerAlgorithm": { "index": 36, "kind": "parameter",
"displayName": "Ssl Trustmanager Algorithm", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "defaultValue": "PKIX", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The algorithm used by the tru [...]
+ "sslTruststoreLocation": { "index": 37, "kind": "parameter",
"displayName": "Ssl Truststore Location", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The location of the trust store file, used to
validate the [...]
+ "sslTruststorePassword": { "index": 38, "kind": "parameter",
"displayName": "Ssl Truststore Password", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": true, "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The password for the trust store file. If a
password is not [...]
+ "sslTruststoreType": { "index": 39, "kind": "parameter", "displayName":
"Ssl Truststore Type", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"JKS", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The file format of the trust store file" }
}
}
diff --git
a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIComponentConfigurer.java
b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIComponentConfigurer.java
index c7d6c5e4cdec..03117979ab1a 100644
---
a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIComponentConfigurer.java
+++
b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIComponentConfigurer.java
@@ -34,6 +34,8 @@ public class OpenAIComponentConfigurer extends
PropertyConfigurerSupport impleme
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "model": target.setModel(property(camelContext,
java.lang.String.class, value)); return true;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters":
target.setUseGlobalSslContextParameters(property(camelContext, boolean.class,
value)); return true;
default: return false;
}
}
@@ -52,6 +54,8 @@ public class OpenAIComponentConfigurer extends
PropertyConfigurerSupport impleme
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
case "model": return java.lang.String.class;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return boolean.class;
default: return null;
}
}
@@ -71,6 +75,8 @@ public class OpenAIComponentConfigurer extends
PropertyConfigurerSupport impleme
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
case "model": return target.getModel();
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return
target.isUseGlobalSslContextParameters();
default: return null;
}
}
diff --git
a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
index 0aea51146fae..4c90c3b38db2 100644
---
a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
+++
b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointConfigurer.java
@@ -63,6 +63,30 @@ public class OpenAIEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "oauthProfile":
target.getConfiguration().setOauthProfile(property(camelContext,
java.lang.String.class, value)); return true;
case "outputclass":
case "outputClass":
target.getConfiguration().setOutputClass(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslcontextparameters":
+ case "sslContextParameters":
target.getConfiguration().setSslContextParameters(property(camelContext,
org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "sslendpointalgorithm":
+ case "sslEndpointAlgorithm":
target.getConfiguration().setSslEndpointAlgorithm(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslkeypassword":
+ case "sslKeyPassword":
target.getConfiguration().setSslKeyPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslkeymanageralgorithm":
+ case "sslKeymanagerAlgorithm":
target.getConfiguration().setSslKeymanagerAlgorithm(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslkeystorelocation":
+ case "sslKeystoreLocation":
target.getConfiguration().setSslKeystoreLocation(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslkeystorepassword":
+ case "sslKeystorePassword":
target.getConfiguration().setSslKeystorePassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslkeystoretype":
+ case "sslKeystoreType":
target.getConfiguration().setSslKeystoreType(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslprotocol":
+ case "sslProtocol":
target.getConfiguration().setSslProtocol(property(camelContext,
java.lang.String.class, value)); return true;
+ case "ssltrustmanageralgorithm":
+ case "sslTrustmanagerAlgorithm":
target.getConfiguration().setSslTrustmanagerAlgorithm(property(camelContext,
java.lang.String.class, value)); return true;
+ case "ssltruststorelocation":
+ case "sslTruststoreLocation":
target.getConfiguration().setSslTruststoreLocation(property(camelContext,
java.lang.String.class, value)); return true;
+ case "ssltruststorepassword":
+ case "sslTruststorePassword":
target.getConfiguration().setSslTruststorePassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "ssltruststoretype":
+ case "sslTruststoreType":
target.getConfiguration().setSslTruststoreType(property(camelContext,
java.lang.String.class, value)); return true;
case "storefullresponse":
case "storeFullResponse":
target.getConfiguration().setStoreFullResponse(property(camelContext,
boolean.class, value)); return true;
case "streaming":
target.getConfiguration().setStreaming(property(camelContext, boolean.class,
value)); return true;
@@ -120,6 +144,30 @@ public class OpenAIEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "oauthProfile": return java.lang.String.class;
case "outputclass":
case "outputClass": return java.lang.String.class;
+ case "sslcontextparameters":
+ case "sslContextParameters": return
org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "sslendpointalgorithm":
+ case "sslEndpointAlgorithm": return java.lang.String.class;
+ case "sslkeypassword":
+ case "sslKeyPassword": return java.lang.String.class;
+ case "sslkeymanageralgorithm":
+ case "sslKeymanagerAlgorithm": return java.lang.String.class;
+ case "sslkeystorelocation":
+ case "sslKeystoreLocation": return java.lang.String.class;
+ case "sslkeystorepassword":
+ case "sslKeystorePassword": return java.lang.String.class;
+ case "sslkeystoretype":
+ case "sslKeystoreType": return java.lang.String.class;
+ case "sslprotocol":
+ case "sslProtocol": return java.lang.String.class;
+ case "ssltrustmanageralgorithm":
+ case "sslTrustmanagerAlgorithm": return java.lang.String.class;
+ case "ssltruststorelocation":
+ case "sslTruststoreLocation": return java.lang.String.class;
+ case "ssltruststorepassword":
+ case "sslTruststorePassword": return java.lang.String.class;
+ case "ssltruststoretype":
+ case "sslTruststoreType": return java.lang.String.class;
case "storefullresponse":
case "storeFullResponse": return boolean.class;
case "streaming": return boolean.class;
@@ -178,6 +226,30 @@ public class OpenAIEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "oauthProfile": return
target.getConfiguration().getOauthProfile();
case "outputclass":
case "outputClass": return target.getConfiguration().getOutputClass();
+ case "sslcontextparameters":
+ case "sslContextParameters": return
target.getConfiguration().getSslContextParameters();
+ case "sslendpointalgorithm":
+ case "sslEndpointAlgorithm": return
target.getConfiguration().getSslEndpointAlgorithm();
+ case "sslkeypassword":
+ case "sslKeyPassword": return
target.getConfiguration().getSslKeyPassword();
+ case "sslkeymanageralgorithm":
+ case "sslKeymanagerAlgorithm": return
target.getConfiguration().getSslKeymanagerAlgorithm();
+ case "sslkeystorelocation":
+ case "sslKeystoreLocation": return
target.getConfiguration().getSslKeystoreLocation();
+ case "sslkeystorepassword":
+ case "sslKeystorePassword": return
target.getConfiguration().getSslKeystorePassword();
+ case "sslkeystoretype":
+ case "sslKeystoreType": return
target.getConfiguration().getSslKeystoreType();
+ case "sslprotocol":
+ case "sslProtocol": return target.getConfiguration().getSslProtocol();
+ case "ssltrustmanageralgorithm":
+ case "sslTrustmanagerAlgorithm": return
target.getConfiguration().getSslTrustmanagerAlgorithm();
+ case "ssltruststorelocation":
+ case "sslTruststoreLocation": return
target.getConfiguration().getSslTruststoreLocation();
+ case "ssltruststorepassword":
+ case "sslTruststorePassword": return
target.getConfiguration().getSslTruststorePassword();
+ case "ssltruststoretype":
+ case "sslTruststoreType": return
target.getConfiguration().getSslTruststoreType();
case "storefullresponse":
case "storeFullResponse": return
target.getConfiguration().isStoreFullResponse();
case "streaming": return target.getConfiguration().isStreaming();
diff --git
a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
index 2098da83bf73..af7361f1ec69 100644
---
a/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
+++
b/components/camel-ai/camel-openai/src/generated/java/org/apache/camel/component/openai/OpenAIEndpointUriFactory.java
@@ -23,7 +23,7 @@ public class OpenAIEndpointUriFactory extends
org.apache.camel.support.component
private static final Set<String> SECRET_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(28);
+ Set<String> props = new HashSet<>(40);
props.add("additionalBodyProperty");
props.add("apiKey");
props.add("autoToolExecution");
@@ -46,6 +46,18 @@ public class OpenAIEndpointUriFactory extends
org.apache.camel.support.component
props.add("oauthProfile");
props.add("operation");
props.add("outputClass");
+ props.add("sslContextParameters");
+ props.add("sslEndpointAlgorithm");
+ props.add("sslKeyPassword");
+ props.add("sslKeymanagerAlgorithm");
+ props.add("sslKeystoreLocation");
+ props.add("sslKeystorePassword");
+ props.add("sslKeystoreType");
+ props.add("sslProtocol");
+ props.add("sslTrustmanagerAlgorithm");
+ props.add("sslTruststoreLocation");
+ props.add("sslTruststorePassword");
+ props.add("sslTruststoreType");
props.add("storeFullResponse");
props.add("streaming");
props.add("systemMessage");
@@ -53,8 +65,11 @@ public class OpenAIEndpointUriFactory extends
org.apache.camel.support.component
props.add("topP");
props.add("userMessage");
PROPERTY_NAMES = Collections.unmodifiableSet(props);
- Set<String> secretProps = new HashSet<>(1);
+ Set<String> secretProps = new HashSet<>(4);
secretProps.add("apiKey");
+ secretProps.add("sslKeyPassword");
+ secretProps.add("sslKeystorePassword");
+ secretProps.add("sslTruststorePassword");
SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps);
Map<String, String> prefixes = new HashMap<>(2);
prefixes.put("additionalBodyProperty", "additionalBodyProperty.");
diff --git
a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
index e9366504a874..feb4bfac903d 100644
---
a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
+++
b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
@@ -29,7 +29,8 @@
"embeddingModel": { "index": 2, "kind": "property", "displayName":
"Embedding Model", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "description": "Default model for embeddings endpoints"
},
"lazyStartProducer": { "index": 3, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
"model": { "index": 4, "kind": "property", "displayName": "Model",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Default model for chat completion endpoints" },
- "autowiredEnabled": { "index": 5, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "autowiredEnabled": { "index": 5, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "useGlobalSslContextParameters": { "index": 6, "kind": "property",
"displayName": "Use Global Ssl Context Parameters", "group": "security",
"label": "security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Enable usage of global SSL context
parameters" }
},
"headers": {
"CamelOpenAIUserMessage": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The user message to send to the OpenAI chat completion
API", "constantName":
"org.apache.camel.component.openai.OpenAIConstants#USER_MESSAGE" },
@@ -89,6 +90,18 @@
"topP": { "index": 24, "kind": "parameter", "displayName": "Top P",
"group": "producer", "label": "", "required": false, "type": "number",
"javaType": "java.lang.Double", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "Top P for response generation (0.0 to 1.0)" },
"userMessage": { "index": 25, "kind": "parameter", "displayName": "User
Message", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "Default user message
text to use when no prompt is provided" },
"lazyStartProducer": { "index": 26, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
- "oauthProfile": { "index": 27, "kind": "parameter", "displayName": "Oauth
Profile", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "OAuth profile name for
obtaining an access token via the OAuth 2.0 Client Cred [...]
+ "oauthProfile": { "index": 27, "kind": "parameter", "displayName": "Oauth
Profile", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "OAuth profile name for
obtaining an access token via the OAuth 2.0 Client Cred [...]
+ "sslContextParameters": { "index": 28, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "SSLContextParameters to
use [...]
+ "sslEndpointAlgorithm": { "index": 29, "kind": "parameter", "displayName":
"Ssl Endpoint Algorithm", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"https", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The endpoint identification algorith [...]
+ "sslKeymanagerAlgorithm": { "index": 30, "kind": "parameter",
"displayName": "Ssl Keymanager Algorithm", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "defaultValue": "SunX509", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The algorithm used by the key [...]
+ "sslKeyPassword": { "index": 31, "kind": "parameter", "displayName": "Ssl
Key Password", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "The password of the
private key in the key store file" },
+ "sslKeystoreLocation": { "index": 32, "kind": "parameter", "displayName":
"Ssl Keystore Location", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "The location of the key
store file. This is optional and can be [...]
+ "sslKeystorePassword": { "index": 33, "kind": "parameter", "displayName":
"Ssl Keystore Password", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true,
"configurationClass": "org.apache.camel.component.openai.OpenAIConfiguration",
"configurationField": "configuration", "description": "The store password for
the key store file" },
+ "sslKeystoreType": { "index": 34, "kind": "parameter", "displayName": "Ssl
Keystore Type", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"JKS", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The file format of the key store file" },
+ "sslProtocol": { "index": 35, "kind": "parameter", "displayName": "Ssl
Protocol", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"TLSv1.3", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The SSL protocol used to generate the
SSLContext" },
+ "sslTrustmanagerAlgorithm": { "index": 36, "kind": "parameter",
"displayName": "Ssl Trustmanager Algorithm", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "defaultValue": "PKIX", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The algorithm used by the tru [...]
+ "sslTruststoreLocation": { "index": 37, "kind": "parameter",
"displayName": "Ssl Truststore Location", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The location of the trust store file, used to
validate the [...]
+ "sslTruststorePassword": { "index": 38, "kind": "parameter",
"displayName": "Ssl Truststore Password", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": true, "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The password for the trust store file. If a
password is not [...]
+ "sslTruststoreType": { "index": 39, "kind": "parameter", "displayName":
"Ssl Truststore Type", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
"JKS", "configurationClass":
"org.apache.camel.component.openai.OpenAIConfiguration", "configurationField":
"configuration", "description": "The file format of the trust store file" }
}
}
diff --git
a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
index 4e09e1d24ecc..51d009eeee5f 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
@@ -327,6 +327,129 @@ from("direct:with-history")
.log("${body}");
----
+== SSL Configuration
+
+The component supports custom SSL/TLS configuration for connecting to OpenAI
or OpenAI-compatible endpoints that use self-signed certificates, private CAs,
or require mutual TLS (mTLS) authentication.
+
+When no SSL parameters are set, the default JVM trust store is used.
+
+=== Using SSLContextParameters
+
+The component implements `SSLContextParametersAware` and supports Camel's
standard `SSLContextParameters` for SSL configuration. When set,
`SSLContextParameters` takes precedence over the individual `ssl*` properties
(same pattern as `camel-kafka`).
+
+[source,java]
+----
+KeyStoreParameters trustStoreParams = new KeyStoreParameters();
+trustStoreParams.setResource("/path/to/truststore.jks");
+trustStoreParams.setPassword("changeit");
+
+TrustManagersParameters trustManagers = new TrustManagersParameters();
+trustManagers.setKeyStore(trustStoreParams);
+
+SSLContextParameters sslContextParameters = new SSLContextParameters();
+sslContextParameters.setTrustManagers(trustManagers);
+
+from("direct:chat")
+ .to("openai:chat-completion?model=gpt-4"
+ + "&baseUrl=https://my-llm-server:8443/v1"
+ + "&sslContextParameters=#sslContextParameters");
+----
+
+To use global SSL context parameters for all OpenAI endpoints:
+
+[source,java]
+----
+OpenAIComponent openai = context.getComponent("openai", OpenAIComponent.class);
+openai.setUseGlobalSslContextParameters(true);
+----
+
+=== Custom Trust Store
+
+To trust a server using a self-signed or private CA certificate:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:chat")
+ .to("openai:chat-completion?model=gpt-4"
+ + "&baseUrl=https://my-llm-server:8443/v1"
+ + "&sslTruststoreLocation=/path/to/truststore.jks"
+ + "&sslTruststorePassword=changeit");
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:chat
+ steps:
+ - to:
+ uri: openai:chat-completion
+ parameters:
+ model: gpt-4
+ baseUrl: https://my-llm-server:8443/v1
+ sslTruststoreLocation: /path/to/truststore.jks
+ sslTruststorePassword: changeit
+----
+====
+
+=== Mutual TLS (mTLS)
+
+For two-way authentication, configure both trust store and key store:
+
+[source,java]
+----
+from("direct:chat")
+ .to("openai:chat-completion?model=gpt-4"
+ + "&baseUrl=https://my-llm-server:8443/v1"
+ + "&sslTruststoreLocation=/path/to/truststore.jks"
+ + "&sslTruststorePassword=changeit"
+ + "&sslKeystoreLocation=/path/to/keystore.jks"
+ + "&sslKeystorePassword=changeit"
+ + "&sslKeyPassword=keypass");
+----
+
+=== Disabling Hostname Verification
+
+In development or test environments, hostname verification can be disabled by
setting `sslEndpointAlgorithm` to an empty string or `none`:
+
+[source,java]
+----
+from("direct:chat")
+ .to("openai:chat-completion?model=gpt-4"
+ + "&baseUrl=https://localhost:8443/v1"
+ + "&sslTruststoreLocation=/path/to/truststore.jks"
+ + "&sslTruststorePassword=changeit"
+ + "&sslEndpointAlgorithm=none");
+----
+
+WARNING: Disabling hostname verification is insecure and should only be used
in non-production environments.
+
+=== SSL Parameters
+
+[cols="1,1,1,3"]
+|===
+| Parameter | Type | Default | Description
+
+| `sslContextParameters` | SSLContextParameters | | Camel SSL context
parameters. When set, takes precedence over the individual `ssl*` options below
+| `sslTruststoreLocation` | String | | Location of the trust store file
+| `sslTruststorePassword` | String | | Trust store password
+| `sslTruststoreType` | String | `JKS` | Trust store format (e.g., `JKS`,
`PKCS12`)
+| `sslKeystoreLocation` | String | | Location of the key store file (for mTLS)
+| `sslKeystorePassword` | String | | Key store password
+| `sslKeystoreType` | String | `JKS` | Key store format (e.g., `JKS`, `PKCS12`)
+| `sslKeyPassword` | String | | Private key password in the key store
+| `sslProtocol` | String | `TLSv1.3` | SSL protocol for generating the
SSLContext
+| `sslKeymanagerAlgorithm` | String | `SunX509` | Algorithm for the key
manager factory
+| `sslTrustmanagerAlgorithm` | String | `PKIX` | Algorithm for the trust
manager factory
+| `sslEndpointAlgorithm` | String | `https` | Hostname verification algorithm;
set to empty or `none` to disable
+|===
+
== Compatibility
This component works with any OpenAI API-compatible endpoint by setting the
`baseUrl` parameter. This includes:
diff --git
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIComponent.java
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIComponent.java
index c3b77be8a807..566f05e00c41 100644
---
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIComponent.java
+++
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIComponent.java
@@ -20,6 +20,7 @@ import java.util.Map;
import com.openai.core.ClientOptions;
import org.apache.camel.Endpoint;
+import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
@@ -28,7 +29,7 @@ import org.apache.camel.support.DefaultComponent;
* OpenAI component for chat completion and embeddings.
*/
@Component("openai")
-public class OpenAIComponent extends DefaultComponent {
+public class OpenAIComponent extends DefaultComponent implements
SSLContextParametersAware {
@Metadata(description = "Default API key for all endpoints")
private String apiKey;
@@ -42,6 +43,10 @@ public class OpenAIComponent extends DefaultComponent {
@Metadata(description = "Default model for embeddings endpoints")
private String embeddingModel;
+ @Metadata(label = "security", defaultValue = "false",
+ description = "Enable usage of global SSL context parameters")
+ private boolean useGlobalSslContextParameters;
+
@Override
protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) throws Exception {
OpenAIConfiguration configuration = new OpenAIConfiguration();
@@ -64,6 +69,10 @@ public class OpenAIComponent extends DefaultComponent {
endpoint.setOperation(remaining);
setProperties(endpoint, parameters);
+ if (configuration.getSslContextParameters() == null) {
+
configuration.setSslContextParameters(retrieveGlobalSslContextParameters());
+ }
+
return endpoint;
}
@@ -98,4 +107,14 @@ public class OpenAIComponent extends DefaultComponent {
public void setEmbeddingModel(String embeddingModel) {
this.embeddingModel = embeddingModel;
}
+
+ @Override
+ public boolean isUseGlobalSslContextParameters() {
+ return useGlobalSslContextParameters;
+ }
+
+ @Override
+ public void setUseGlobalSslContextParameters(boolean
useGlobalSslContextParameters) {
+ this.useGlobalSslContextParameters = useGlobalSslContextParameters;
+ }
}
diff --git
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
index 6cb9bea4943c..7b25ee7b549f 100644
---
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
+++
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConfiguration.java
@@ -22,6 +22,7 @@ import com.openai.core.ClientOptions;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriParams;
+import org.apache.camel.support.jsse.SSLContextParameters;
/**
* Configuration for OpenAI component.
@@ -152,6 +153,60 @@ public class OpenAIConfiguration implements Cloneable {
@Metadata(description = "The format for embedding output: 'float' for list
of floats, 'base64' for compressed format")
private String encodingFormat = "base64";
+ // ========== SSL CONFIGURATION ==========
+
+ @UriParam(label = "security")
+ @Metadata(description = "SSLContextParameters to use for configuring
SSL/TLS. "
+ + "When set, takes precedence over the individual
sslTruststore*, sslKeystore*, and sslProtocol options.")
+ private SSLContextParameters sslContextParameters;
+
+ @UriParam(label = "security")
+ @Metadata(description = "The location of the trust store file, used to
validate the server's certificate")
+ private String sslTruststoreLocation;
+
+ @UriParam(label = "security", secret = true)
+ @Metadata(description = "The password for the trust store file. If a
password is not set, the configured trust store can still "
+ + "be used, but integrity checking is disabled")
+ private String sslTruststorePassword;
+
+ @UriParam(label = "security", defaultValue = "JKS")
+ @Metadata(description = "The file format of the trust store file")
+ private String sslTruststoreType = "JKS";
+
+ @UriParam(label = "security")
+ @Metadata(description = "The location of the key store file. This is
optional and can be used for two-way authentication "
+ + "for the OpenAI API")
+ private String sslKeystoreLocation;
+
+ @UriParam(label = "security", secret = true)
+ @Metadata(description = "The store password for the key store file")
+ private String sslKeystorePassword;
+
+ @UriParam(label = "security", defaultValue = "JKS")
+ @Metadata(description = "The file format of the key store file")
+ private String sslKeystoreType = "JKS";
+
+ @UriParam(label = "security", secret = true)
+ @Metadata(description = "The password of the private key in the key store
file")
+ private String sslKeyPassword;
+
+ @UriParam(label = "security", defaultValue = "TLSv1.3")
+ @Metadata(description = "The SSL protocol used to generate the SSLContext")
+ private String sslProtocol = "TLSv1.3";
+
+ @UriParam(label = "security", defaultValue = "SunX509")
+ @Metadata(description = "The algorithm used by the key manager factory for
SSL connections")
+ private String sslKeymanagerAlgorithm = "SunX509";
+
+ @UriParam(label = "security", defaultValue = "PKIX")
+ @Metadata(description = "The algorithm used by the trust manager factory
for SSL connections")
+ private String sslTrustmanagerAlgorithm = "PKIX";
+
+ @UriParam(label = "security", defaultValue = "https")
+ @Metadata(description = "The endpoint identification algorithm to validate
the server hostname using the server certificate. "
+ + "Set to an empty string or 'none' to disable
hostname verification")
+ private String sslEndpointAlgorithm = "https";
+
public String getApiKey() {
return apiKey;
}
@@ -360,6 +415,102 @@ public class OpenAIConfiguration implements Cloneable {
this.mcpReconnect = mcpReconnect;
}
+ public SSLContextParameters getSslContextParameters() {
+ return sslContextParameters;
+ }
+
+ public void setSslContextParameters(SSLContextParameters
sslContextParameters) {
+ this.sslContextParameters = sslContextParameters;
+ }
+
+ public String getSslTruststoreLocation() {
+ return sslTruststoreLocation;
+ }
+
+ public void setSslTruststoreLocation(String sslTruststoreLocation) {
+ this.sslTruststoreLocation = sslTruststoreLocation;
+ }
+
+ public String getSslTruststorePassword() {
+ return sslTruststorePassword;
+ }
+
+ public void setSslTruststorePassword(String sslTruststorePassword) {
+ this.sslTruststorePassword = sslTruststorePassword;
+ }
+
+ public String getSslTruststoreType() {
+ return sslTruststoreType;
+ }
+
+ public void setSslTruststoreType(String sslTruststoreType) {
+ this.sslTruststoreType = sslTruststoreType;
+ }
+
+ public String getSslKeystoreLocation() {
+ return sslKeystoreLocation;
+ }
+
+ public void setSslKeystoreLocation(String sslKeystoreLocation) {
+ this.sslKeystoreLocation = sslKeystoreLocation;
+ }
+
+ public String getSslKeystorePassword() {
+ return sslKeystorePassword;
+ }
+
+ public void setSslKeystorePassword(String sslKeystorePassword) {
+ this.sslKeystorePassword = sslKeystorePassword;
+ }
+
+ public String getSslKeystoreType() {
+ return sslKeystoreType;
+ }
+
+ public void setSslKeystoreType(String sslKeystoreType) {
+ this.sslKeystoreType = sslKeystoreType;
+ }
+
+ public String getSslKeyPassword() {
+ return sslKeyPassword;
+ }
+
+ public void setSslKeyPassword(String sslKeyPassword) {
+ this.sslKeyPassword = sslKeyPassword;
+ }
+
+ public String getSslProtocol() {
+ return sslProtocol;
+ }
+
+ public void setSslProtocol(String sslProtocol) {
+ this.sslProtocol = sslProtocol;
+ }
+
+ public String getSslKeymanagerAlgorithm() {
+ return sslKeymanagerAlgorithm;
+ }
+
+ public void setSslKeymanagerAlgorithm(String sslKeymanagerAlgorithm) {
+ this.sslKeymanagerAlgorithm = sslKeymanagerAlgorithm;
+ }
+
+ public String getSslTrustmanagerAlgorithm() {
+ return sslTrustmanagerAlgorithm;
+ }
+
+ public void setSslTrustmanagerAlgorithm(String sslTrustmanagerAlgorithm) {
+ this.sslTrustmanagerAlgorithm = sslTrustmanagerAlgorithm;
+ }
+
+ public String getSslEndpointAlgorithm() {
+ return sslEndpointAlgorithm;
+ }
+
+ public void setSslEndpointAlgorithm(String sslEndpointAlgorithm) {
+ this.sslEndpointAlgorithm = sslEndpointAlgorithm;
+ }
+
public OpenAIConfiguration copy() {
try {
return (OpenAIConfiguration) clone();
diff --git
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
index eac39672e95d..b2be34ceda7c 100644
---
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
+++
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEndpoint.java
@@ -16,7 +16,9 @@
*/
package org.apache.camel.component.openai;
+import java.io.FileInputStream;
import java.net.http.HttpRequest;
+import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
@@ -25,6 +27,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.chat.completions.ChatCompletionFunctionTool;
@@ -369,9 +378,105 @@ public class OpenAIEndpoint extends DefaultEndpoint {
builder.baseUrl(ObjectHelper.notNullOrEmpty(configuration.getBaseUrl(),
"baseUrl"));
+ configureSsl(builder);
+
return builder.build();
}
+ private void configureSsl(OpenAIOkHttpClient.Builder builder) throws
Exception {
+ // SSLContextParameters takes precedence over individual SSL properties
+ if (configuration.getSslContextParameters() != null) {
+ configureSslFromContextParameters(builder,
configuration.getSslContextParameters());
+ return;
+ }
+
+ configureSslFromProperties(builder);
+ }
+
+ private void configureSslFromContextParameters(
+ OpenAIOkHttpClient.Builder builder,
+ org.apache.camel.support.jsse.SSLContextParameters
sslContextParameters)
+ throws Exception {
+ SSLContext sslContext =
sslContextParameters.createSSLContext(getCamelContext());
+
+ // OpenAIOkHttpClient requires both sslSocketFactory and trustManager
to be set together
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init((KeyStore) null);
+ X509TrustManager x509TrustManager = (X509TrustManager)
tmf.getTrustManagers()[0];
+
+ // If SSLContextParameters has trust managers configured, try to
extract them
+ if (sslContextParameters.getTrustManagers() != null) {
+ TrustManager[] trustManagers =
sslContextParameters.getTrustManagers().createTrustManagers();
+ if (trustManagers != null && trustManagers.length > 0 &&
trustManagers[0] instanceof X509TrustManager) {
+ x509TrustManager = (X509TrustManager) trustManagers[0];
+ }
+ }
+
+ builder.sslSocketFactory(sslContext.getSocketFactory());
+ builder.trustManager(x509TrustManager);
+ }
+
+ private void configureSslFromProperties(OpenAIOkHttpClient.Builder
builder) throws Exception {
+ boolean hasTrustStore =
ObjectHelper.isNotEmpty(configuration.getSslTruststoreLocation());
+ boolean hasKeyStore =
ObjectHelper.isNotEmpty(configuration.getSslKeystoreLocation());
+
+ if (!hasTrustStore && !hasKeyStore) {
+ return;
+ }
+
+ TrustManager[] trustManagers = null;
+ if (hasTrustStore) {
+ KeyStore trustStore =
KeyStore.getInstance(configuration.getSslTruststoreType());
+ char[] trustStorePassword =
configuration.getSslTruststorePassword() != null
+ ? configuration.getSslTruststorePassword().toCharArray() :
null;
+ try (FileInputStream fis = new
FileInputStream(configuration.getSslTruststoreLocation())) {
+ trustStore.load(fis, trustStorePassword);
+ }
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(configuration.getSslTrustmanagerAlgorithm());
+ tmf.init(trustStore);
+ trustManagers = tmf.getTrustManagers();
+ }
+
+ KeyManager[] keyManagers = null;
+ if (hasKeyStore) {
+ KeyStore keyStore =
KeyStore.getInstance(configuration.getSslKeystoreType());
+ char[] keyStorePassword = configuration.getSslKeystorePassword()
!= null
+ ? configuration.getSslKeystorePassword().toCharArray() :
null;
+ try (FileInputStream fis = new
FileInputStream(configuration.getSslKeystoreLocation())) {
+ keyStore.load(fis, keyStorePassword);
+ }
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(configuration.getSslKeymanagerAlgorithm());
+ char[] keyPassword = configuration.getSslKeyPassword() != null
+ ? configuration.getSslKeyPassword().toCharArray() :
keyStorePassword;
+ kmf.init(keyStore, keyPassword);
+ keyManagers = kmf.getKeyManagers();
+ }
+
+ SSLContext sslContext =
SSLContext.getInstance(configuration.getSslProtocol());
+ sslContext.init(keyManagers, trustManagers, null);
+
+ // OpenAIOkHttpClient requires both sslSocketFactory and trustManager
to be set together
+ X509TrustManager x509TrustManager;
+ if (trustManagers != null) {
+ x509TrustManager = (X509TrustManager) trustManagers[0];
+ } else {
+ // When only keystore is configured, use the default trust manager
+ TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ defaultTmf.init((KeyStore) null);
+ x509TrustManager = (X509TrustManager)
defaultTmf.getTrustManagers()[0];
+ }
+
+ builder.sslSocketFactory(sslContext.getSocketFactory());
+ builder.trustManager(x509TrustManager);
+
+ // Configure hostname verification
+ String endpointAlgorithm = configuration.getSslEndpointAlgorithm();
+ if (ObjectHelper.isEmpty(endpointAlgorithm) ||
"none".equalsIgnoreCase(endpointAlgorithm)) {
+ builder.hostnameVerifier((hostname, session) -> true);
+ }
+ }
+
protected String resolveApiKey() throws Exception {
// Priority: URI parameter > OAuth profile > environment variable >
system property
if (ObjectHelper.isNotEmpty(configuration.getApiKey())) {
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIMtlsMockTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIMtlsMockTest.java
new file mode 100644
index 000000000000..92daaf92a6c2
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIMtlsMockTest.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.openai;
+
+import java.io.FileInputStream;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import com.sun.net.httpserver.HttpsServer;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.infra.openai.mock.MockExpectation;
+import org.apache.camel.test.infra.openai.mock.OpenAIMockServerHandler;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class OpenAIMtlsMockTest extends CamelTestSupport {
+
+ private static final String SSL_RESOURCES =
"src/test/resources/org/apache/camel/component/openai/ssl/";
+ private static final String KEYSTORE_PATH = SSL_RESOURCES +
"test-keystore.jks";
+ private static final String TRUSTSTORE_PATH = SSL_RESOURCES +
"test-truststore.jks";
+ private static final String KEYSTORE_DIFFPASS_PATH = SSL_RESOURCES +
"test-keystore-diffpass.jks";
+ private static final String TRUSTSTORE_DIFFPASS_PATH = SSL_RESOURCES +
"test-truststore-diffpass.jks";
+ private static final String STORE_PASSWORD = "changeit";
+ private static final String KEY_PASSWORD = "keypass123";
+
+ private HttpsServer httpsServer;
+ private ExecutorService executor;
+ private int port;
+
+ @BeforeEach
+ void startMtlsServer() throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(KEYSTORE_PATH)) {
+ keyStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, STORE_PASSWORD.toCharArray());
+
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(TRUSTSTORE_PATH)) {
+ trustStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(trustStore);
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ httpsServer = HttpsServer.create(new InetSocketAddress(0), 0);
+ httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
+ @Override
+ public void configure(HttpsParameters params) {
+ SSLParameters sslParams = sslContext.getDefaultSSLParameters();
+ sslParams.setNeedClientAuth(true);
+ params.setSSLParameters(sslParams);
+ }
+ });
+
+ MockExpectation expectation = new MockExpectation("hello");
+ expectation.setExpectedResponse("Hello over mTLS!");
+ List<MockExpectation> expectations = new ArrayList<>();
+ expectations.add(expectation);
+
+ httpsServer.createContext("/",
+ new OpenAIMockServerHandler(expectations, List.of(), new
ObjectMapper()));
+
+ executor = Executors.newSingleThreadExecutor();
+ httpsServer.setExecutor(executor);
+ httpsServer.start();
+ port = httpsServer.getAddress().getPort();
+ }
+
+ @AfterEach
+ void stopMtlsServer() {
+ if (httpsServer != null) {
+ httpsServer.stop(0);
+ executor.shutdownNow();
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:chat-mtls")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslKeystoreLocation=" + KEYSTORE_PATH
+ + "&sslKeystorePassword=" + STORE_PASSWORD
+ + "&sslKeyPassword=" + STORE_PASSWORD);
+
+ from("direct:chat-mtls-no-client-cert")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD);
+
+ from("direct:chat-mtls-key-password-fallback")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslKeystoreLocation=" + KEYSTORE_PATH
+ + "&sslKeystorePassword=" + STORE_PASSWORD);
+
+ from("direct:chat-mtls-diffpass")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" +
TRUSTSTORE_DIFFPASS_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslKeystoreLocation=" + KEYSTORE_DIFFPASS_PATH
+ + "&sslKeystorePassword=" + STORE_PASSWORD
+ + "&sslKeyPassword=" + KEY_PASSWORD);
+
+ from("direct:chat-mtls-diffpass-wrong-keypass")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" +
TRUSTSTORE_DIFFPASS_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslKeystoreLocation=" + KEYSTORE_DIFFPASS_PATH
+ + "&sslKeystorePassword=" + STORE_PASSWORD);
+ }
+ };
+ }
+
+ @Test
+ void chatOverMutualTls() {
+ Exchange result = template.request("direct:chat-mtls", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over mTLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ assertEquals("openai-mock",
result.getMessage().getHeader(OpenAIConstants.RESPONSE_MODEL));
+ }
+
+ @Test
+ void chatOverMutualTlsWithKeyPasswordFallback() {
+ Exchange result =
template.request("direct:chat-mtls-key-password-fallback", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(),
+ "Exchange should not have an exception when sslKeyPassword
falls back to sslKeystorePassword");
+ assertEquals("Hello over mTLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ }
+
+ @Test
+ void chatOverMutualTlsWithDifferentKeyPassword() throws Exception {
+ HttpsServer diffPassServer = startMtlsServer(KEYSTORE_DIFFPASS_PATH,
TRUSTSTORE_DIFFPASS_PATH, KEY_PASSWORD);
+ int diffPassPort = diffPassServer.getAddress().getPort();
+ try {
+ Exchange result = template.request("direct:chat-mtls-diffpass", e
-> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", diffPassPort);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over mTLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ } finally {
+ diffPassServer.stop(0);
+ }
+ }
+
+ @Test
+ void chatOverMutualTlsWithWrongKeyPasswordFallbackFails() throws Exception
{
+ HttpsServer diffPassServer = startMtlsServer(KEYSTORE_DIFFPASS_PATH,
TRUSTSTORE_DIFFPASS_PATH, KEY_PASSWORD);
+ int diffPassPort = diffPassServer.getAddress().getPort();
+ try {
+ Exchange result =
template.request("direct:chat-mtls-diffpass-wrong-keypass", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", diffPassPort);
+ });
+ assertNotNull(result.getException(),
+ "Exchange should fail when sslKeyPassword is not set and
key password differs from store password");
+ } finally {
+ diffPassServer.stop(0);
+ }
+ }
+
+ @Test
+ void chatOverMutualTlsWithoutClientCertFails() {
+ Exchange result = template.request("direct:chat-mtls-no-client-cert",
e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNotNull(result.getException(),
+ "Exchange should fail when server requires client certificate
but none is provided");
+ }
+
+ private HttpsServer startMtlsServer(String keystorePath, String
truststorePath, String keyPassword) throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(keystorePath)) {
+ keyStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, keyPassword.toCharArray());
+
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(truststorePath)) {
+ trustStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(trustStore);
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ HttpsServer server = HttpsServer.create(new InetSocketAddress(0), 0);
+ server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
+ @Override
+ public void configure(HttpsParameters params) {
+ SSLParameters sslParams = sslContext.getDefaultSSLParameters();
+ sslParams.setNeedClientAuth(true);
+ params.setSSLParameters(sslParams);
+ }
+ });
+
+ MockExpectation expectation = new MockExpectation("hello");
+ expectation.setExpectedResponse("Hello over mTLS!");
+ List<MockExpectation> expectations = new ArrayList<>();
+ expectations.add(expectation);
+
+ server.createContext("/",
+ new OpenAIMockServerHandler(expectations, List.of(), new
ObjectMapper()));
+
+ server.setExecutor(Executors.newSingleThreadExecutor());
+ server.start();
+ return server;
+ }
+}
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslConfigurationTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslConfigurationTest.java
new file mode 100644
index 000000000000..c84dc91b90fd
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslConfigurationTest.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.openai;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class OpenAISslConfigurationTest extends CamelTestSupport {
+
+ private static final String TRUSTSTORE_PATH
+ =
"src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.jks";
+ private static final String KEYSTORE_PATH =
"src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.jks";
+ private static final String STORE_PASSWORD = "changeit";
+
+ @Test
+ void sslDefaultValues() {
+ OpenAIConfiguration config = new OpenAIConfiguration();
+
+ assertEquals("JKS", config.getSslTruststoreType());
+ assertEquals("JKS", config.getSslKeystoreType());
+ assertEquals("TLSv1.3", config.getSslProtocol());
+ assertEquals("SunX509", config.getSslKeymanagerAlgorithm());
+ assertEquals("PKIX", config.getSslTrustmanagerAlgorithm());
+ assertEquals("https", config.getSslEndpointAlgorithm());
+ }
+
+ @Test
+ void sslConfigurationFromUri() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD
+ +
"&sslTruststoreType=JKS"
+ +
"&sslKeystoreLocation=" + KEYSTORE_PATH
+ +
"&sslKeystorePassword=" + STORE_PASSWORD
+ +
"&sslKeystoreType=JKS"
+ +
"&sslKeyPassword=" + STORE_PASSWORD
+ +
"&sslProtocol=TLSv1.3"
+ +
"&sslKeymanagerAlgorithm=SunX509"
+ +
"&sslTrustmanagerAlgorithm=PKIX"
+ +
"&sslEndpointAlgorithm=none");
+
+ OpenAIConfiguration config = endpoint.getConfiguration();
+
+ assertEquals(TRUSTSTORE_PATH, config.getSslTruststoreLocation());
+ assertEquals(STORE_PASSWORD, config.getSslTruststorePassword());
+ assertEquals("JKS", config.getSslTruststoreType());
+ assertEquals(KEYSTORE_PATH, config.getSslKeystoreLocation());
+ assertEquals(STORE_PASSWORD, config.getSslKeystorePassword());
+ assertEquals("JKS", config.getSslKeystoreType());
+ assertEquals(STORE_PASSWORD, config.getSslKeyPassword());
+ assertEquals("TLSv1.3", config.getSslProtocol());
+ assertEquals("SunX509", config.getSslKeymanagerAlgorithm());
+ assertEquals("PKIX", config.getSslTrustmanagerAlgorithm());
+ assertEquals("none", config.getSslEndpointAlgorithm());
+ }
+
+ @Test
+ void createClientWithTrustStore() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD);
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithKeyStore() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslKeystoreLocation=" + KEYSTORE_PATH
+ +
"&sslKeystorePassword=" + STORE_PASSWORD);
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithBothStores() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD
+ +
"&sslKeystoreLocation=" + KEYSTORE_PATH
+ +
"&sslKeystorePassword=" + STORE_PASSWORD
+ +
"&sslKeyPassword=" + STORE_PASSWORD);
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithDisabledHostnameVerification() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD
+ +
"&sslEndpointAlgorithm=none");
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithNoSslConfigUsesDefaults() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy");
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithInvalidTruststoreLocationFails() {
+ CamelContext camelContext = context();
+
+ Exception exception = assertThrows(Exception.class,
+ () -> camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=/nonexistent/truststore.jks"
+ + "&sslTruststorePassword=" +
STORE_PASSWORD));
+
+ assertHasRootCause(exception, java.io.FileNotFoundException.class);
+ }
+
+ @Test
+ void createClientWithInvalidKeystoreLocationFails() {
+ CamelContext camelContext = context();
+
+ Exception exception = assertThrows(Exception.class,
+ () -> camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslKeystoreLocation=/nonexistent/keystore.jks"
+ + "&sslKeystorePassword=" +
STORE_PASSWORD));
+
+ assertHasRootCause(exception, java.io.FileNotFoundException.class);
+ }
+
+ @Test
+ void createClientWithInvalidTruststoreTypeFails() {
+ CamelContext camelContext = context();
+
+ Exception exception = assertThrows(Exception.class,
+ () -> camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ + "&sslTruststoreLocation=" +
TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" +
STORE_PASSWORD
+ +
"&sslTruststoreType=INVALID"));
+
+ assertHasRootCause(exception, java.security.KeyStoreException.class);
+ }
+
+ private static void assertHasRootCause(Throwable throwable, Class<?
extends Throwable> expectedCauseType) {
+ Throwable cause = throwable;
+ while (cause != null) {
+ if (expectedCauseType.isInstance(cause)) {
+ return;
+ }
+ cause = cause.getCause();
+ }
+ throw new AssertionError(
+ "Expected root cause of type " + expectedCauseType.getName()
+ + " but was: " +
throwable.getClass().getName() + ": " + throwable.getMessage());
+ }
+
+ @Test
+ void createClientWithEmptyEndpointAlgorithmDisablesHostnameVerification()
throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD
+ +
"&sslEndpointAlgorithm=");
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithKeyStoreAndNoKeyPasswordFallsBackToStorePassword()
throws Exception {
+ CamelContext camelContext = context();
+ // sslKeyPassword is not set, should fall back to sslKeystorePassword
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslKeystoreLocation=" + KEYSTORE_PATH
+ +
"&sslKeystorePassword=" + STORE_PASSWORD);
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithTlsV12Protocol() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD
+ +
"&sslProtocol=TLSv1.2");
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void createClientWithCustomAlgorithms() throws Exception {
+ CamelContext camelContext = context();
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ +
"&sslTruststorePassword=" + STORE_PASSWORD
+ +
"&sslTrustmanagerAlgorithm=SunX509"
+ +
"&sslKeystoreLocation=" + KEYSTORE_PATH
+ +
"&sslKeystorePassword=" + STORE_PASSWORD
+ +
"&sslKeymanagerAlgorithm=SunX509");
+
+ assertNotNull(endpoint.createClient());
+ }
+
+ @Test
+ void sslConfigurationCopy() {
+ OpenAIConfiguration config = new OpenAIConfiguration();
+ config.setSslTruststoreLocation("/path/to/truststore");
+ config.setSslTruststorePassword("secret");
+ config.setSslTruststoreType("PKCS12");
+ config.setSslKeystoreLocation("/path/to/keystore");
+ config.setSslKeystorePassword("secret2");
+ config.setSslKeystoreType("PKCS12");
+ config.setSslKeyPassword("keypass");
+ config.setSslProtocol("TLSv1.2");
+ config.setSslKeymanagerAlgorithm("PKIX");
+ config.setSslTrustmanagerAlgorithm("SunX509");
+ config.setSslEndpointAlgorithm("none");
+
+ OpenAIConfiguration copy = config.copy();
+
+ assertEquals(config.getSslTruststoreLocation(),
copy.getSslTruststoreLocation());
+ assertEquals(config.getSslTruststorePassword(),
copy.getSslTruststorePassword());
+ assertEquals(config.getSslTruststoreType(),
copy.getSslTruststoreType());
+ assertEquals(config.getSslKeystoreLocation(),
copy.getSslKeystoreLocation());
+ assertEquals(config.getSslKeystorePassword(),
copy.getSslKeystorePassword());
+ assertEquals(config.getSslKeystoreType(), copy.getSslKeystoreType());
+ assertEquals(config.getSslKeyPassword(), copy.getSslKeyPassword());
+ assertEquals(config.getSslProtocol(), copy.getSslProtocol());
+ assertEquals(config.getSslKeymanagerAlgorithm(),
copy.getSslKeymanagerAlgorithm());
+ assertEquals(config.getSslTrustmanagerAlgorithm(),
copy.getSslTrustmanagerAlgorithm());
+ assertEquals(config.getSslEndpointAlgorithm(),
copy.getSslEndpointAlgorithm());
+ }
+}
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslContextParametersTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslContextParametersTest.java
new file mode 100644
index 000000000000..ef0d37722d61
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslContextParametersTest.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.openai;
+
+import java.io.FileInputStream;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.jsse.KeyStoreParameters;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.apache.camel.support.jsse.TrustManagersParameters;
+import org.apache.camel.test.infra.openai.mock.MockExpectation;
+import org.apache.camel.test.infra.openai.mock.OpenAIMockServerHandler;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class OpenAISslContextParametersTest extends CamelTestSupport {
+
+ private static final String SSL_RESOURCES =
"src/test/resources/org/apache/camel/component/openai/ssl/";
+ private static final String KEYSTORE_PATH = SSL_RESOURCES +
"test-keystore.jks";
+ private static final String TRUSTSTORE_PATH = SSL_RESOURCES +
"test-truststore.jks";
+ private static final String STORE_PASSWORD = "changeit";
+
+ private HttpsServer httpsServer;
+ private ExecutorService executor;
+ private int port;
+
+ @BeforeEach
+ void startHttpsServer() throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(KEYSTORE_PATH)) {
+ keyStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, STORE_PASSWORD.toCharArray());
+
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(TRUSTSTORE_PATH)) {
+ trustStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(trustStore);
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ httpsServer = HttpsServer.create(new InetSocketAddress(0), 0);
+ httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+
+ MockExpectation expectation = new MockExpectation("hello");
+ expectation.setExpectedResponse("Hello over TLS!");
+ List<MockExpectation> expectations = new ArrayList<>();
+ expectations.add(expectation);
+
+ httpsServer.createContext("/",
+ new OpenAIMockServerHandler(expectations, List.of(), new
ObjectMapper()));
+
+ executor = Executors.newSingleThreadExecutor();
+ httpsServer.setExecutor(executor);
+ httpsServer.start();
+ port = httpsServer.getAddress().getPort();
+ }
+
+ @AfterEach
+ void stopHttpsServer() {
+ if (httpsServer != null) {
+ httpsServer.stop(0);
+ executor.shutdownNow();
+ }
+ }
+
+ @Test
+ void sslContextParametersTakesPrecedenceOverIndividualProperties() throws
Exception {
+ CamelContext camelContext = context();
+
+ // Configure endpoint with BOTH SSLContextParameters and individual
properties pointing to a non-existent file.
+ // If SSLContextParameters takes precedence, the non-existent
truststore path is ignored and the test succeeds.
+ SSLContextParameters sslContextParameters =
createSslContextParameters();
+
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&baseUrl=https://localhost:" + port + "/v1"
+ +
"&sslTruststoreLocation=/nonexistent/truststore.jks"
+ +
"&sslTruststorePassword=bogus"
+ +
"&sslEndpointAlgorithm=https");
+
+
endpoint.getConfiguration().setSslContextParameters(sslContextParameters);
+
+ assertNotNull(endpoint.createClient(),
+ "Client should be created using SSLContextParameters, ignoring
invalid properties");
+ }
+
+ @Test
+ void endpointSslContextParametersTakesPrecedenceOverGlobal() throws
Exception {
+ SSLContextParameters endpointParams = createSslContextParameters();
+ SSLContextParameters globalParams = new SSLContextParameters();
+
+ CamelContext camelContext = context();
+ camelContext.setSSLContextParameters(globalParams);
+
+ OpenAIComponent component = (OpenAIComponent)
camelContext.getComponent("openai");
+ component.setUseGlobalSslContextParameters(true);
+
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&baseUrl=https://localhost:" + port + "/v1"
+ +
"&sslEndpointAlgorithm=https");
+
+ endpoint.getConfiguration().setSslContextParameters(endpointParams);
+
+ assertSame(endpointParams,
endpoint.getConfiguration().getSslContextParameters(),
+ "Endpoint-level SSLContextParameters should not be overwritten
by global");
+ }
+
+ @Test
+ void globalSslContextParametersUsedWhenEndpointHasNone() throws Exception {
+ SSLContextParameters globalParams = createSslContextParameters();
+
+ CamelContext camelContext = context();
+ camelContext.setSSLContextParameters(globalParams);
+
+ OpenAIComponent component = (OpenAIComponent)
camelContext.getComponent("openai");
+ component.setUseGlobalSslContextParameters(true);
+
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&baseUrl=https://localhost:" + port + "/v1"
+ +
"&sslEndpointAlgorithm=https");
+
+ assertSame(globalParams,
endpoint.getConfiguration().getSslContextParameters(),
+ "Global SSLContextParameters should be used when endpoint has
none");
+ }
+
+ @Test
+ void globalSslContextParametersNotUsedWhenFlagDisabled() throws Exception {
+ SSLContextParameters globalParams = createSslContextParameters();
+
+ CamelContext camelContext = context();
+ camelContext.setSSLContextParameters(globalParams);
+
+ OpenAIComponent component = (OpenAIComponent)
camelContext.getComponent("openai");
+ component.setUseGlobalSslContextParameters(false);
+
+ OpenAIEndpoint endpoint = (OpenAIEndpoint) camelContext.getEndpoint(
+ "openai:chat-completion?apiKey=dummy"
+ +
"&baseUrl=https://localhost:" + port + "/v1");
+
+ assertNull(endpoint.getConfiguration().getSslContextParameters(),
+ "Global SSLContextParameters should not be used when
useGlobalSslContextParameters is false");
+ }
+
+ @Test
+ void chatOverTlsWithSslContextParameters() {
+ Exchange result = template.request("direct:chat-ssl-context-params", e
-> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over TLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ }
+
+ @Test
+ void componentImplementsSslContextParametersAware() {
+ OpenAIComponent component = (OpenAIComponent)
context().getComponent("openai");
+ assertTrue(component instanceof
org.apache.camel.SSLContextParametersAware,
+ "OpenAIComponent should implement SSLContextParametersAware");
+ }
+
+ @Test
+ void fallbackToIndividualPropertiesWhenNoSslContextParameters() {
+ Exchange result = template.request("direct:chat-ssl-individual-props",
e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception when using individual SSL properties");
+ assertEquals("Hello over TLS!",
result.getMessage().getBody(String.class));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:chat-ssl-context-params")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslEndpointAlgorithm=https");
+
+ from("direct:chat-ssl-individual-props")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslEndpointAlgorithm=https");
+ }
+ };
+ }
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext camelContext = super.createCamelContext();
+
+ // Set global SSLContextParameters for the route-based test
+ SSLContextParameters globalParams = createSslContextParameters();
+ camelContext.setSSLContextParameters(globalParams);
+
+ OpenAIComponent component = (OpenAIComponent)
camelContext.getComponent("openai");
+ component.setUseGlobalSslContextParameters(true);
+
+ return camelContext;
+ }
+
+ private SSLContextParameters createSslContextParameters() {
+ KeyStoreParameters trustStoreParams = new KeyStoreParameters();
+ trustStoreParams.setResource(TRUSTSTORE_PATH);
+ trustStoreParams.setPassword(STORE_PASSWORD);
+ trustStoreParams.setType("JKS");
+
+ TrustManagersParameters trustManagersParams = new
TrustManagersParameters();
+ trustManagersParams.setKeyStore(trustStoreParams);
+
+ SSLContextParameters sslContextParameters = new SSLContextParameters();
+ sslContextParameters.setTrustManagers(trustManagersParams);
+ sslContextParameters.setSecureSocketProtocol("TLSv1.3");
+
+ return sslContextParameters;
+ }
+}
diff --git
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslMockTest.java
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslMockTest.java
new file mode 100644
index 000000000000..0f8bf4d16e43
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAISslMockTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.openai;
+
+import java.io.FileInputStream;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.infra.openai.mock.MockExpectation;
+import org.apache.camel.test.infra.openai.mock.OpenAIMockServerHandler;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class OpenAISslMockTest extends CamelTestSupport {
+
+ private static final String SSL_RESOURCES =
"src/test/resources/org/apache/camel/component/openai/ssl/";
+ private static final String KEYSTORE_PATH = SSL_RESOURCES +
"test-keystore.jks";
+ private static final String TRUSTSTORE_PATH = SSL_RESOURCES +
"test-truststore.jks";
+ private static final String KEYSTORE_P12_PATH = SSL_RESOURCES +
"test-keystore.p12";
+ private static final String TRUSTSTORE_P12_PATH = SSL_RESOURCES +
"test-truststore.p12";
+ private static final String STORE_PASSWORD = "changeit";
+
+ private HttpsServer httpsServer;
+ private ExecutorService executor;
+ private int port;
+
+ @BeforeEach
+ void startHttpsServer() throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(KEYSTORE_PATH)) {
+ keyStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, STORE_PASSWORD.toCharArray());
+
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(TRUSTSTORE_PATH)) {
+ trustStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(trustStore);
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ httpsServer = HttpsServer.create(new InetSocketAddress(0), 0);
+ httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+
+ MockExpectation expectation = new MockExpectation("hello");
+ expectation.setExpectedResponse("Hello over TLS!");
+ List<MockExpectation> expectations = new ArrayList<>();
+ expectations.add(expectation);
+
+ httpsServer.createContext("/",
+ new OpenAIMockServerHandler(expectations, List.of(), new
ObjectMapper()));
+
+ executor = Executors.newSingleThreadExecutor();
+ httpsServer.setExecutor(executor);
+ httpsServer.start();
+ port = httpsServer.getAddress().getPort();
+ }
+
+ @AfterEach
+ void stopHttpsServer() {
+ if (httpsServer != null) {
+ httpsServer.stop(0);
+ executor.shutdownNow();
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:chat-ssl")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslEndpointAlgorithm=https");
+
+ from("direct:chat-ssl-no-verify")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslEndpointAlgorithm=none");
+
+ from("direct:chat-ssl-empty-algorithm")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslEndpointAlgorithm=");
+
+ from("direct:chat-no-ssl")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1");
+
+ from("direct:chat-ssl-pkcs12")
+ .toD("openai:chat-completion?model=gpt-5&apiKey=dummy"
+ + "&baseUrl=https://localhost:${header.port}/v1"
+ + "&sslTruststoreLocation=" + TRUSTSTORE_P12_PATH
+ + "&sslTruststorePassword=" + STORE_PASSWORD
+ + "&sslTruststoreType=PKCS12");
+ }
+ };
+ }
+
+ @Test
+ void chatOverTlsWithTrustStore() {
+ Exchange result = template.request("direct:chat-ssl", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over TLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ assertEquals("openai-mock",
result.getMessage().getHeader(OpenAIConstants.RESPONSE_MODEL));
+ }
+
+ @Test
+ void chatOverTlsWithHostnameVerificationDisabled() {
+ Exchange result = template.request("direct:chat-ssl-no-verify", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over TLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ }
+
+ @Test
+ void chatOverTlsWithEmptyEndpointAlgorithm() {
+ Exchange result = template.request("direct:chat-ssl-empty-algorithm",
e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over TLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ }
+
+ @Test
+ void chatOverTlsWithPkcs12TrustStore() throws Exception {
+ HttpsServer pkcs12Server = startHttpsServer(KEYSTORE_P12_PATH,
TRUSTSTORE_P12_PATH, "PKCS12");
+ int pkcs12Port = pkcs12Server.getAddress().getPort();
+ try {
+ Exchange result = template.request("direct:chat-ssl-pkcs12", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", pkcs12Port);
+ });
+ assertNull(result.getException(), "Exchange should not have an
exception");
+ assertEquals("Hello over TLS!",
result.getMessage().getBody(String.class));
+
assertNotNull(result.getMessage().getHeader(OpenAIConstants.RESPONSE_ID));
+ assertEquals("openai-mock",
result.getMessage().getHeader(OpenAIConstants.RESPONSE_MODEL));
+ } finally {
+ pkcs12Server.stop(0);
+ }
+ }
+
+ @Test
+ void chatOverTlsWithoutTrustStoreFailsWithSslError() {
+ Exchange result = template.request("direct:chat-no-ssl", e -> {
+ e.getIn().setBody("hello");
+ e.getIn().setHeader("port", port);
+ });
+ assertNotNull(result.getException(), "Exchange should fail with SSL
error when no trust store is configured");
+ }
+
+ private HttpsServer startHttpsServer(String keystorePath, String
truststorePath, String storeType) throws Exception {
+ KeyStore keyStore = KeyStore.getInstance(storeType);
+ try (FileInputStream fis = new FileInputStream(keystorePath)) {
+ keyStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, STORE_PASSWORD.toCharArray());
+
+ KeyStore trustStore = KeyStore.getInstance(storeType);
+ try (FileInputStream fis = new FileInputStream(truststorePath)) {
+ trustStore.load(fis, STORE_PASSWORD.toCharArray());
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(trustStore);
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ HttpsServer server = HttpsServer.create(new InetSocketAddress(0), 0);
+ server.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+
+ MockExpectation expectation = new MockExpectation("hello");
+ expectation.setExpectedResponse("Hello over TLS!");
+ List<MockExpectation> expectations = new ArrayList<>();
+ expectations.add(expectation);
+
+ server.createContext("/",
+ new OpenAIMockServerHandler(expectations, List.of(), new
ObjectMapper()));
+
+ server.setExecutor(Executors.newSingleThreadExecutor());
+ server.start();
+ return server;
+ }
+}
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/README.md
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/README.md
new file mode 100644
index 000000000000..0f6dd22dd94f
--- /dev/null
+++
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/README.md
@@ -0,0 +1,67 @@
+# Test Resources
+
+## SSL Test Keystores
+
+### JKS keystores (test-keystore.jks / test-truststore.jks)
+
+Used by `OpenAISslConfigurationTest`, `OpenAISslMockTest`, and
`OpenAIMtlsMockTest`.
+Store password and key password are both `changeit`.
+
+```bash
+keytool -genkeypair -alias test -keyalg RSA -keysize 2048 -validity 3650 \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.jks \
+ -storepass changeit -keypass changeit \
+ -dname "CN=localhost, OU=Test, O=Apache, L=Test, ST=Test, C=US" \
+ -ext "SAN=dns:localhost,ip:127.0.0.1"
+
+keytool -exportcert -alias test \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.jks
-storepass changeit \
+ -file /tmp/test-cert.cer
+
+keytool -importcert -alias test -file /tmp/test-cert.cer \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.jks
-storepass changeit -noprompt
+```
+
+### JKS keystores with different key password (test-keystore-diffpass.jks /
test-truststore-diffpass.jks)
+
+Used by `OpenAIMtlsMockTest` to test the `sslKeyPassword` fallback behavior.
+Store password is `changeit`, key password is `keypass123`.
+
+```bash
+keytool -genkeypair -alias test -keyalg RSA -keysize 2048 -validity 3650 \
+ -storetype JKS \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-keystore-diffpass.jks
\
+ -storepass changeit -keypass keypass123 \
+ -dname "CN=localhost, OU=Test, O=Apache, L=Test, ST=Test, C=US" \
+ -ext "SAN=dns:localhost,ip:127.0.0.1"
+
+keytool -exportcert -alias test \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-keystore-diffpass.jks
-storepass changeit \
+ -file /tmp/test-cert-diffpass.cer
+
+keytool -importcert -alias test -file /tmp/test-cert-diffpass.cer \
+ -storetype JKS \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-truststore-diffpass.jks
-storepass changeit -noprompt
+```
+
+### PKCS12 keystores (test-keystore.p12 / test-truststore.p12)
+
+Used by `OpenAISslMockTest` to test `sslTruststoreType=PKCS12`.
+Store password is `changeit`.
+
+```bash
+keytool -genkeypair -alias test -keyalg RSA -keysize 2048 -validity 3650 \
+ -storetype PKCS12 \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.p12 \
+ -storepass changeit \
+ -dname "CN=localhost, OU=Test, O=Apache, L=Test, ST=Test, C=US" \
+ -ext "SAN=dns:localhost,ip:127.0.0.1"
+
+keytool -exportcert -alias test \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.p12
-storepass changeit \
+ -file /tmp/test-cert-p12.cer
+
+keytool -importcert -alias test -file /tmp/test-cert-p12.cer \
+ -storetype PKCS12 \
+ -keystore
src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.p12
-storepass changeit -noprompt
+```
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore-diffpass.jks
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore-diffpass.jks
new file mode 100644
index 000000000000..da4a84576e31
Binary files /dev/null and
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore-diffpass.jks
differ
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.jks
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.jks
new file mode 100644
index 000000000000..05777dcd57af
Binary files /dev/null and
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.jks
differ
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.p12
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.p12
new file mode 100644
index 000000000000..7dee8343d506
Binary files /dev/null and
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-keystore.p12
differ
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore-diffpass.jks
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore-diffpass.jks
new file mode 100644
index 000000000000..81e63a04c14d
Binary files /dev/null and
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore-diffpass.jks
differ
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.jks
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.jks
new file mode 100644
index 000000000000..12166a656d03
Binary files /dev/null and
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.jks
differ
diff --git
a/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.p12
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.p12
new file mode 100644
index 000000000000..59df9353f316
Binary files /dev/null and
b/components/camel-ai/camel-openai/src/test/resources/org/apache/camel/component/openai/ssl/test-truststore.p12
differ
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
index 4928fb0bdf99..27e93f41ffda 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
@@ -158,6 +158,23 @@ public interface OpenaiComponentBuilderFactory {
doSetProperty("autowiredEnabled", autowiredEnabled);
return this;
}
+
+
+ /**
+ * Enable usage of global SSL context parameters.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param useGlobalSslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpenaiComponentBuilder useGlobalSslContextParameters(boolean
useGlobalSslContextParameters) {
+ doSetProperty("useGlobalSslContextParameters",
useGlobalSslContextParameters);
+ return this;
+ }
}
class OpenaiComponentBuilderImpl
@@ -179,6 +196,7 @@ public interface OpenaiComponentBuilderFactory {
case "lazyStartProducer": ((OpenAIComponent)
component).setLazyStartProducer((boolean) value); return true;
case "model": ((OpenAIComponent)
component).setModel((java.lang.String) value); return true;
case "autowiredEnabled": ((OpenAIComponent)
component).setAutowiredEnabled((boolean) value); return true;
+ case "useGlobalSslContextParameters": ((OpenAIComponent)
component).setUseGlobalSslContextParameters((boolean) value); return true;
default: return false;
}
}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
index 497eff805214..d719573604a7 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
@@ -675,6 +675,206 @@ public interface OpenAIEndpointBuilderFactory {
doSetProperty("oauthProfile", oauthProfile);
return this;
}
+ /**
+ * SSLContextParameters to use for configuring SSL/TLS. When set, takes
+ * precedence over the individual sslTruststore, sslKeystore, and
+ * sslProtocol options.
+ *
+ * The option is a:
+ * <code>org.apache.camel.support.jsse.SSLContextParameters</code>
type.
+ *
+ * Group: security
+ *
+ * @param sslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder
sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters
sslContextParameters) {
+ doSetProperty("sslContextParameters", sslContextParameters);
+ return this;
+ }
+ /**
+ * SSLContextParameters to use for configuring SSL/TLS. When set, takes
+ * precedence over the individual sslTruststore, sslKeystore, and
+ * sslProtocol options.
+ *
+ * The option will be converted to a
+ * <code>org.apache.camel.support.jsse.SSLContextParameters</code>
type.
+ *
+ * Group: security
+ *
+ * @param sslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslContextParameters(String
sslContextParameters) {
+ doSetProperty("sslContextParameters", sslContextParameters);
+ return this;
+ }
+ /**
+ * The endpoint identification algorithm to validate the server
hostname
+ * using the server certificate. Set to an empty string or 'none' to
+ * disable hostname verification.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Default: https
+ * Group: security
+ *
+ * @param sslEndpointAlgorithm the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslEndpointAlgorithm(String
sslEndpointAlgorithm) {
+ doSetProperty("sslEndpointAlgorithm", sslEndpointAlgorithm);
+ return this;
+ }
+ /**
+ * The algorithm used by the key manager factory for SSL connections.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Default: SunX509
+ * Group: security
+ *
+ * @param sslKeymanagerAlgorithm the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslKeymanagerAlgorithm(String
sslKeymanagerAlgorithm) {
+ doSetProperty("sslKeymanagerAlgorithm", sslKeymanagerAlgorithm);
+ return this;
+ }
+ /**
+ * The password of the private key in the key store file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param sslKeyPassword the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslKeyPassword(String sslKeyPassword) {
+ doSetProperty("sslKeyPassword", sslKeyPassword);
+ return this;
+ }
+ /**
+ * The location of the key store file. This is optional and can be used
+ * for two-way authentication for the OpenAI API.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param sslKeystoreLocation the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslKeystoreLocation(String
sslKeystoreLocation) {
+ doSetProperty("sslKeystoreLocation", sslKeystoreLocation);
+ return this;
+ }
+ /**
+ * The store password for the key store file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param sslKeystorePassword the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslKeystorePassword(String
sslKeystorePassword) {
+ doSetProperty("sslKeystorePassword", sslKeystorePassword);
+ return this;
+ }
+ /**
+ * The file format of the key store file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Default: JKS
+ * Group: security
+ *
+ * @param sslKeystoreType the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslKeystoreType(String sslKeystoreType) {
+ doSetProperty("sslKeystoreType", sslKeystoreType);
+ return this;
+ }
+ /**
+ * The SSL protocol used to generate the SSLContext.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Default: TLSv1.3
+ * Group: security
+ *
+ * @param sslProtocol the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslProtocol(String sslProtocol) {
+ doSetProperty("sslProtocol", sslProtocol);
+ return this;
+ }
+ /**
+ * The algorithm used by the trust manager factory for SSL connections.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Default: PKIX
+ * Group: security
+ *
+ * @param sslTrustmanagerAlgorithm the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslTrustmanagerAlgorithm(String
sslTrustmanagerAlgorithm) {
+ doSetProperty("sslTrustmanagerAlgorithm",
sslTrustmanagerAlgorithm);
+ return this;
+ }
+ /**
+ * The location of the trust store file, used to validate the server's
+ * certificate.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param sslTruststoreLocation the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslTruststoreLocation(String
sslTruststoreLocation) {
+ doSetProperty("sslTruststoreLocation", sslTruststoreLocation);
+ return this;
+ }
+ /**
+ * The password for the trust store file. If a password is not set, the
+ * configured trust store can still be used, but integrity checking is
+ * disabled.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param sslTruststorePassword the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslTruststorePassword(String
sslTruststorePassword) {
+ doSetProperty("sslTruststorePassword", sslTruststorePassword);
+ return this;
+ }
+ /**
+ * The file format of the trust store file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Default: JKS
+ * Group: security
+ *
+ * @param sslTruststoreType the value to set
+ * @return the dsl builder
+ */
+ default OpenAIEndpointBuilder sslTruststoreType(String
sslTruststoreType) {
+ doSetProperty("sslTruststoreType", sslTruststoreType);
+ return this;
+ }
}
/**