This is an automated email from the ASF dual-hosted git repository.
oscerd 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 4ef53c0e71a3 CAMEL-23849: Camel-PQC - add automated key rotation
scheduling (#24455)
4ef53c0e71a3 is described below
commit 4ef53c0e71a3eb163133ab2ec15c140eac61b9b7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Jul 8 23:03:05 2026 +0200
CAMEL-23849: Camel-PQC - add automated key rotation scheduling (#24455)
* CAMEL-23849: Camel-PQC add automated key rotation scheduling
Add KeyRotationScheduler, an opt-in background service that periodically
evaluates the keys of a KeyLifecycleManager and rotates the ones that need
rotation according to a configurable policy (check interval, max age, max
usage, key filter). Rotated keys are replaced by a freshly generated key
and the previous key is deprecated. The scheduler is a Camel Service (can
be added to the CamelContext), notifies an optional KeyRotationListener on
each rotation and error, and exposes counters for monitoring.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* CAMEL-23849: Address review - CamelContext ExecutorServiceManager and
component options
Review feedback on PR #24455:
- KeyRotationScheduler implements CamelContextAware and, when a
CamelContext is
present, obtains its scheduled executor from the ExecutorServiceManager
(with a
standalone Executors fallback for non-Camel use); shutdown also goes
through the
manager.
- Add camel.component.pqc.* options (keyRotationSchedulerEnabled,
keyRotationCheckInterval, keyRotationMaxAge, keyRotationMaxUsage) so the
scheduler
can be configured and started without Java, reusing the existing
keyLifecycleManager
component option. Regenerated catalog and component-dsl.
- Docs: property-driven configuration example.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../org/apache/camel/catalog/components/pqc.json | 26 +-
.../component/pqc/PQCComponentConfigurer.java | 24 ++
.../org/apache/camel/component/pqc/pqc.json | 26 +-
.../camel-pqc/src/main/docs/pqc-key-lifecycle.adoc | 67 ++++
.../apache/camel/component/pqc/PQCComponent.java | 99 ++++++
.../pqc/lifecycle/KeyRotationScheduler.java | 360 +++++++++++++++++++++
.../pqc/lifecycle/KeyRotationSchedulerTest.java | 284 ++++++++++++++++
.../component/dsl/PqcComponentBuilderFactory.java | 73 +++++
8 files changed, 937 insertions(+), 22 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
index 067244919a3f..a57b6015bc51 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
@@ -39,17 +39,21 @@
"keyLifecycleManager": { "index": 12, "kind": "property", "displayName":
"Key Lifecycle Manager", "group": "advanced", "label": "advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "The KeyLifecycleManager
to [...]
"keyPair": { "index": 13, "kind": "property", "displayName": "Key Pair",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.KeyPair", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The KeyPair to be used" },
"keyPairAlias": { "index": 14, "kind": "property", "displayName": "Key
Pair Alias", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "A KeyPair alias to use
in combination with KeyStore parameter" },
- "keyStore": { "index": 15, "kind": "property", "displayName": "Key Store",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "A KeyStore where we could get Cryptographic
material" },
- "keyStorePassword": { "index": 16, "kind": "property", "displayName": "Key
Store Password", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true, "security":
"secret", "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The KeyStore password to use in combination
with KeySto [...]
- "signatureAlgorithm": { "index": 17, "kind": "property", "displayName":
"Signature Algorithm", "group": "advanced", "label": "advanced", "required":
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA",
"SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC",
"SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "c [...]
- "signer": { "index": 18, "kind": "property", "displayName": "Signer",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.Signature", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The Signer to be used" },
- "statefulKeyWarningThreshold": { "index": 19, "kind": "property",
"displayName": "Stateful Key Warning Threshold", "group": "advanced", "label":
"advanced", "required": false, "type": "number", "javaType": "double",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "defaultValue": 0.1, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The warning threshold for stateful key exh
[...]
- "storeExtractedSecretKeyAsHeader": { "index": 20, "kind": "property",
"displayName": "Store Extracted Secret Key As Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "In the context of extractSec [...]
- "strictKeyLifecycle": { "index": 21, "kind": "property", "displayName":
"Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "Whether to enforce key
status checks before cryptographic [...]
- "symmetricKeyAlgorithm": { "index": 22, "kind": "property", "displayName":
"Symmetric Key Algorithm", "group": "advanced", "label": "advanced",
"required": false, "type": "enum", "javaType": "java.lang.String", "enum": [
"AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539",
"DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256",
"SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "co [...]
- "symmetricKeyLength": { "index": 23, "kind": "property", "displayName":
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required":
false, "type": "integer", "javaType": "int", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "The required length of
the symmetric key used" },
- "healthCheckConsumerEnabled": { "index": 24, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
- "healthCheckProducerEnabled": { "index": 25, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
+ "keyRotationCheckInterval": { "index": 15, "kind": "property",
"displayName": "Key Rotation Check Interval", "group": "advanced", "label":
"advanced", "required": false, "type": "duration", "javaType": "long",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"3600000", "description": "Interval between key rotation checks when the
scheduler is enabled." },
+ "keyRotationMaxAge": { "index": 16, "kind": "property", "displayName":
"Key Rotation Max Age", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "description": "When the scheduler is
enabled, rotate keys older than this age. If not set, age is not used as a
rotation signal." },
+ "keyRotationMaxUsage": { "index": 17, "kind": "property", "displayName":
"Key Rotation Max Usage", "group": "advanced", "label": "advanced", "required":
false, "type": "integer", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": 0, "description": "When the scheduler
is enabled, rotate keys whose recorded usage count reaches this value. 0
disables usage-based rotation." },
+ "keyRotationSchedulerEnabled": { "index": 18, "kind": "property",
"displayName": "Key Rotation Scheduler Enabled", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to start an automated background key rotation
scheduler for this component. Requires keyLifecycleManager to be set. The
scheduler periodically rotates keys that exce [...]
+ "keyStore": { "index": 19, "kind": "property", "displayName": "Key Store",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "A KeyStore where we could get Cryptographic
material" },
+ "keyStorePassword": { "index": 20, "kind": "property", "displayName": "Key
Store Password", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true, "security":
"secret", "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The KeyStore password to use in combination
with KeySto [...]
+ "signatureAlgorithm": { "index": 21, "kind": "property", "displayName":
"Signature Algorithm", "group": "advanced", "label": "advanced", "required":
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA",
"SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC",
"SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "c [...]
+ "signer": { "index": 22, "kind": "property", "displayName": "Signer",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.Signature", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The Signer to be used" },
+ "statefulKeyWarningThreshold": { "index": 23, "kind": "property",
"displayName": "Stateful Key Warning Threshold", "group": "advanced", "label":
"advanced", "required": false, "type": "number", "javaType": "double",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "defaultValue": 0.1, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The warning threshold for stateful key exh
[...]
+ "storeExtractedSecretKeyAsHeader": { "index": 24, "kind": "property",
"displayName": "Store Extracted Secret Key As Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "In the context of extractSec [...]
+ "strictKeyLifecycle": { "index": 25, "kind": "property", "displayName":
"Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "Whether to enforce key
status checks before cryptographic [...]
+ "symmetricKeyAlgorithm": { "index": 26, "kind": "property", "displayName":
"Symmetric Key Algorithm", "group": "advanced", "label": "advanced",
"required": false, "type": "enum", "javaType": "java.lang.String", "enum": [
"AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539",
"DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256",
"SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "co [...]
+ "symmetricKeyLength": { "index": 27, "kind": "property", "displayName":
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required":
false, "type": "integer", "javaType": "int", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "The required length of
the symmetric key used" },
+ "healthCheckConsumerEnabled": { "index": 28, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
+ "healthCheckProducerEnabled": { "index": 29, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
},
"headers": {
"CamelPQCOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation we want to perform", "constantName":
"org.apache.camel.component.pqc.PQCConstants#OPERATION" },
diff --git
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
index 39e46f037f72..4799514a49fa 100644
---
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
+++
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
@@ -59,6 +59,14 @@ public class PQCComponentConfigurer extends
PropertyConfigurerSupport implements
case "keyPair":
getOrCreateConfiguration(target).setKeyPair(property(camelContext,
java.security.KeyPair.class, value)); return true;
case "keypairalias":
case "keyPairAlias":
getOrCreateConfiguration(target).setKeyPairAlias(property(camelContext,
java.lang.String.class, value)); return true;
+ case "keyrotationcheckinterval":
+ case "keyRotationCheckInterval":
target.setKeyRotationCheckInterval(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
+ case "keyrotationmaxage":
+ case "keyRotationMaxAge":
target.setKeyRotationMaxAge(property(camelContext, java.time.Duration.class,
value).toMillis()); return true;
+ case "keyrotationmaxusage":
+ case "keyRotationMaxUsage":
target.setKeyRotationMaxUsage(property(camelContext, long.class, value));
return true;
+ case "keyrotationschedulerenabled":
+ case "keyRotationSchedulerEnabled":
target.setKeyRotationSchedulerEnabled(property(camelContext, boolean.class,
value)); return true;
case "keystore":
case "keyStore":
getOrCreateConfiguration(target).setKeyStore(property(camelContext,
java.security.KeyStore.class, value)); return true;
case "keystorepassword":
@@ -120,6 +128,14 @@ public class PQCComponentConfigurer extends
PropertyConfigurerSupport implements
case "keyPair": return java.security.KeyPair.class;
case "keypairalias":
case "keyPairAlias": return java.lang.String.class;
+ case "keyrotationcheckinterval":
+ case "keyRotationCheckInterval": return long.class;
+ case "keyrotationmaxage":
+ case "keyRotationMaxAge": return long.class;
+ case "keyrotationmaxusage":
+ case "keyRotationMaxUsage": return long.class;
+ case "keyrotationschedulerenabled":
+ case "keyRotationSchedulerEnabled": return boolean.class;
case "keystore":
case "keyStore": return java.security.KeyStore.class;
case "keystorepassword":
@@ -177,6 +193,14 @@ public class PQCComponentConfigurer extends
PropertyConfigurerSupport implements
case "keyPair": return getOrCreateConfiguration(target).getKeyPair();
case "keypairalias":
case "keyPairAlias": return
getOrCreateConfiguration(target).getKeyPairAlias();
+ case "keyrotationcheckinterval":
+ case "keyRotationCheckInterval": return
target.getKeyRotationCheckInterval();
+ case "keyrotationmaxage":
+ case "keyRotationMaxAge": return target.getKeyRotationMaxAge();
+ case "keyrotationmaxusage":
+ case "keyRotationMaxUsage": return target.getKeyRotationMaxUsage();
+ case "keyrotationschedulerenabled":
+ case "keyRotationSchedulerEnabled": return
target.isKeyRotationSchedulerEnabled();
case "keystore":
case "keyStore": return getOrCreateConfiguration(target).getKeyStore();
case "keystorepassword":
diff --git
a/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
b/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
index 067244919a3f..a57b6015bc51 100644
---
a/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
+++
b/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
@@ -39,17 +39,21 @@
"keyLifecycleManager": { "index": 12, "kind": "property", "displayName":
"Key Lifecycle Manager", "group": "advanced", "label": "advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "The KeyLifecycleManager
to [...]
"keyPair": { "index": 13, "kind": "property", "displayName": "Key Pair",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.KeyPair", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The KeyPair to be used" },
"keyPairAlias": { "index": 14, "kind": "property", "displayName": "Key
Pair Alias", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "A KeyPair alias to use
in combination with KeyStore parameter" },
- "keyStore": { "index": 15, "kind": "property", "displayName": "Key Store",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "A KeyStore where we could get Cryptographic
material" },
- "keyStorePassword": { "index": 16, "kind": "property", "displayName": "Key
Store Password", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true, "security":
"secret", "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The KeyStore password to use in combination
with KeySto [...]
- "signatureAlgorithm": { "index": 17, "kind": "property", "displayName":
"Signature Algorithm", "group": "advanced", "label": "advanced", "required":
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA",
"SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC",
"SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "c [...]
- "signer": { "index": 18, "kind": "property", "displayName": "Signer",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.Signature", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The Signer to be used" },
- "statefulKeyWarningThreshold": { "index": 19, "kind": "property",
"displayName": "Stateful Key Warning Threshold", "group": "advanced", "label":
"advanced", "required": false, "type": "number", "javaType": "double",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "defaultValue": 0.1, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The warning threshold for stateful key exh
[...]
- "storeExtractedSecretKeyAsHeader": { "index": 20, "kind": "property",
"displayName": "Store Extracted Secret Key As Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "In the context of extractSec [...]
- "strictKeyLifecycle": { "index": 21, "kind": "property", "displayName":
"Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "Whether to enforce key
status checks before cryptographic [...]
- "symmetricKeyAlgorithm": { "index": 22, "kind": "property", "displayName":
"Symmetric Key Algorithm", "group": "advanced", "label": "advanced",
"required": false, "type": "enum", "javaType": "java.lang.String", "enum": [
"AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539",
"DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256",
"SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "co [...]
- "symmetricKeyLength": { "index": 23, "kind": "property", "displayName":
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required":
false, "type": "integer", "javaType": "int", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "The required length of
the symmetric key used" },
- "healthCheckConsumerEnabled": { "index": 24, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
- "healthCheckProducerEnabled": { "index": 25, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
+ "keyRotationCheckInterval": { "index": 15, "kind": "property",
"displayName": "Key Rotation Check Interval", "group": "advanced", "label":
"advanced", "required": false, "type": "duration", "javaType": "long",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"3600000", "description": "Interval between key rotation checks when the
scheduler is enabled." },
+ "keyRotationMaxAge": { "index": 16, "kind": "property", "displayName":
"Key Rotation Max Age", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "description": "When the scheduler is
enabled, rotate keys older than this age. If not set, age is not used as a
rotation signal." },
+ "keyRotationMaxUsage": { "index": 17, "kind": "property", "displayName":
"Key Rotation Max Usage", "group": "advanced", "label": "advanced", "required":
false, "type": "integer", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": 0, "description": "When the scheduler
is enabled, rotate keys whose recorded usage count reaches this value. 0
disables usage-based rotation." },
+ "keyRotationSchedulerEnabled": { "index": 18, "kind": "property",
"displayName": "Key Rotation Scheduler Enabled", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to start an automated background key rotation
scheduler for this component. Requires keyLifecycleManager to be set. The
scheduler periodically rotates keys that exce [...]
+ "keyStore": { "index": 19, "kind": "property", "displayName": "Key Store",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "A KeyStore where we could get Cryptographic
material" },
+ "keyStorePassword": { "index": 20, "kind": "property", "displayName": "Key
Store Password", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": true, "security":
"secret", "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The KeyStore password to use in combination
with KeySto [...]
+ "signatureAlgorithm": { "index": 21, "kind": "property", "displayName":
"Signature Algorithm", "group": "advanced", "label": "advanced", "required":
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA",
"SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC",
"SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "c [...]
+ "signer": { "index": 22, "kind": "property", "displayName": "Signer",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "java.security.Signature", "deprecated": false, "deprecationNote":
"", "autowired": true, "secret": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The Signer to be used" },
+ "statefulKeyWarningThreshold": { "index": 23, "kind": "property",
"displayName": "Stateful Key Warning Threshold", "group": "advanced", "label":
"advanced", "required": false, "type": "number", "javaType": "double",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "defaultValue": 0.1, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "The warning threshold for stateful key exh
[...]
+ "storeExtractedSecretKeyAsHeader": { "index": 24, "kind": "property",
"displayName": "Store Extracted Secret Key As Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField":
"configuration", "description": "In the context of extractSec [...]
+ "strictKeyLifecycle": { "index": 25, "kind": "property", "displayName":
"Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "Whether to enforce key
status checks before cryptographic [...]
+ "symmetricKeyAlgorithm": { "index": 26, "kind": "property", "displayName":
"Symmetric Key Algorithm", "group": "advanced", "label": "advanced",
"required": false, "type": "enum", "javaType": "java.lang.String", "enum": [
"AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539",
"DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256",
"SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "co [...]
+ "symmetricKeyLength": { "index": 27, "kind": "property", "displayName":
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required":
false, "type": "integer", "javaType": "int", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration",
"configurationField": "configuration", "description": "The required length of
the symmetric key used" },
+ "healthCheckConsumerEnabled": { "index": 28, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
+ "healthCheckProducerEnabled": { "index": 29, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
},
"headers": {
"CamelPQCOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation we want to perform", "constantName":
"org.apache.camel.component.pqc.PQCConstants#OPERATION" },
diff --git a/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
b/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
index 9ef0d21b04d6..adbd2af4c146 100644
--- a/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
+++ b/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
@@ -816,6 +816,73 @@ if (needsRotation) {
}
----
+=== Automated Rotation Scheduling
+
+Rather than calling `needsRotation()`/`rotateKey()` by hand, the
`KeyRotationScheduler` can evaluate all managed keys
+periodically in the background and rotate the ones that breach a configurable
policy. It is opt-in: nothing runs until
+the scheduler is started.
+
+On every tick the scheduler lists the keys of its `KeyLifecycleManager`, keeps
the ones matching the key filter (by
+default only `ACTIVE` keys) and, for each, delegates the decision to
`needsRotation(keyId, maxKeyAge, maxKeyUsage)`. A
+key that needs rotation is replaced by a freshly generated key while the
previous key is deprecated. At least one
+rotation signal must be configured - a maximum age, a maximum usage, or a
per-key `nextRotationAt`/`PENDING_ROTATION`
+status recorded in the key metadata - otherwise no key will ever be selected.
+
+._Java-only: scheduling automated key rotation_
+[source,java]
+----
+KeyLifecycleManager keyManager = new
FileBasedKeyLifecycleManager("/secure/keys");
+
+KeyRotationScheduler scheduler = new KeyRotationScheduler(keyManager)
+ .setCheckInterval(Duration.ofHours(6)) // how often to evaluate the keys
(default: 1 hour)
+ .setMaxKeyAge(Duration.ofDays(90)) // rotate keys older than 90 days
+ .setMaxKeyUsage(1_000_000) // and/or rotate keys used more
than 1,000,000 times
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(String oldKeyId, String newKeyId, KeyMetadata
previous, KeyPair newKeyPair) {
+ logger.info("Rotated {} -> {}", oldKeyId, newKeyId);
+ }
+ });
+
+scheduler.start(); // begin the background schedule
+// ... application runs ...
+scheduler.stop(); // stop the background schedule
+----
+
+Because `KeyRotationScheduler` is a Camel `Service`, it can instead be added
to the `CamelContext` so that it is started
+and stopped together with the context:
+
+._Java-only: managing the scheduler with the CamelContext_
+[source,java]
+----
+camelContext.addService(scheduler); // started and stopped with the context
+----
+
+Alternatively, the scheduler can be started and configured entirely through
component options - no Java code - by setting
+`camel.component.pqc.*` properties (for example in `application.properties`).
Bind a `KeyLifecycleManager` in the
+registry and enable the scheduler:
+
+._application.properties: property-driven key rotation_
+[source,properties]
+----
+# a KeyLifecycleManager bean must be available in the registry, e.g.
#myKeyManager
+camel.component.pqc.key-lifecycle-manager = #myKeyManager
+camel.component.pqc.key-rotation-scheduler-enabled = true
+camel.component.pqc.key-rotation-check-interval = 6h
+camel.component.pqc.key-rotation-max-age = 90d
+camel.component.pqc.key-rotation-max-usage = 1000000
+----
+
+When `keyRotationSchedulerEnabled` is `true`, the `pqc` component builds a
`KeyRotationScheduler` around the configured
+`keyLifecycleManager` and starts and stops it with the component. The same
options are available in the YAML DSL and as
+`PQCComponent` setters, which makes the scheduler usable from Spring Boot and
Quarkus without any Java code.
+
+The default id of a replacement key is the previous key id with a rotation
marker and timestamp appended; supply a
+`setKeyIdStrategy(Function<KeyMetadata, String>)` to control it, and a
`setKeyFilter(Predicate<KeyMetadata>)` to change
+which keys are considered. The scheduler also exposes counters
(`getChecksPerformed()`, `getRotationsPerformed()`,
+`getRotationFailures()`, `getLastCheckAt()`) for monitoring. A single pass can
be triggered on demand with
+`checkAndRotate()`.
+
== Key Export and Import
Keys can be exported and imported in multiple formats:
diff --git
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
index 6734fc1eb1d3..a8e9942bd882 100644
---
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
+++
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.pqc;
+import java.time.Duration;
import java.util.Map;
import org.apache.camel.CamelContext;
@@ -23,6 +24,8 @@ import org.apache.camel.Endpoint;
import org.apache.camel.component.pqc.crypto.*;
import org.apache.camel.component.pqc.crypto.hybrid.*;
import org.apache.camel.component.pqc.crypto.kem.*;
+import org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager;
+import org.apache.camel.component.pqc.lifecycle.KeyRotationScheduler;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.HealthCheckComponent;
@@ -36,6 +39,24 @@ public class PQCComponent extends HealthCheckComponent {
@Metadata
private PQCConfiguration configuration = new PQCConfiguration();
+ @Metadata(label = "advanced", defaultValue = "false",
+ description = "Whether to start an automated background key
rotation scheduler for this component."
+ + " Requires keyLifecycleManager to be set. The
scheduler periodically rotates keys that"
+ + " exceed the configured age and/or usage
policy.")
+ private boolean keyRotationSchedulerEnabled;
+ @Metadata(label = "advanced", defaultValue = "3600000", javaType =
"java.time.Duration",
+ description = "Interval between key rotation checks when the
scheduler is enabled.")
+ private long keyRotationCheckInterval = 3600000L;
+ @Metadata(label = "advanced", javaType = "java.time.Duration",
+ description = "When the scheduler is enabled, rotate keys older
than this age. If not set, age is not used"
+ + " as a rotation signal.")
+ private long keyRotationMaxAge;
+ @Metadata(label = "advanced", defaultValue = "0",
+ description = "When the scheduler is enabled, rotate keys whose
recorded usage count reaches this value. 0"
+ + " disables usage-based rotation.")
+ private long keyRotationMaxUsage;
+
+ private KeyRotationScheduler keyRotationScheduler;
public PQCComponent() {
this(null);
@@ -260,6 +281,31 @@ public class PQCComponent extends HealthCheckComponent {
}
}
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ if (keyRotationSchedulerEnabled) {
+ KeyLifecycleManager manager = configuration != null ?
configuration.getKeyLifecycleManager() : null;
+ ObjectHelper.notNull(manager,
+ "keyLifecycleManager (required when
keyRotationSchedulerEnabled=true)");
+ keyRotationScheduler = new KeyRotationScheduler(manager)
+
.setCheckInterval(Duration.ofMillis(keyRotationCheckInterval))
+ .setMaxKeyAge(keyRotationMaxAge > 0 ?
Duration.ofMillis(keyRotationMaxAge) : null)
+ .setMaxKeyUsage(keyRotationMaxUsage);
+ keyRotationScheduler.setCamelContext(getCamelContext());
+ keyRotationScheduler.start();
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ if (keyRotationScheduler != null) {
+ keyRotationScheduler.stop();
+ keyRotationScheduler = null;
+ }
+ super.doStop();
+ }
+
public PQCConfiguration getConfiguration() {
return configuration;
}
@@ -270,4 +316,57 @@ public class PQCComponent extends HealthCheckComponent {
public void setConfiguration(PQCConfiguration configuration) {
this.configuration = configuration;
}
+
+ public boolean isKeyRotationSchedulerEnabled() {
+ return keyRotationSchedulerEnabled;
+ }
+
+ /**
+ * Whether to start an automated background key rotation scheduler for
this component. Requires keyLifecycleManager
+ * to be set. The scheduler periodically rotates keys that exceed the
configured age and/or usage policy.
+ */
+ public void setKeyRotationSchedulerEnabled(boolean
keyRotationSchedulerEnabled) {
+ this.keyRotationSchedulerEnabled = keyRotationSchedulerEnabled;
+ }
+
+ public long getKeyRotationCheckInterval() {
+ return keyRotationCheckInterval;
+ }
+
+ /**
+ * Interval between key rotation checks when the scheduler is enabled.
+ */
+ public void setKeyRotationCheckInterval(long keyRotationCheckInterval) {
+ this.keyRotationCheckInterval = keyRotationCheckInterval;
+ }
+
+ public long getKeyRotationMaxAge() {
+ return keyRotationMaxAge;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys older than this age. If not
set, age is not used as a rotation signal.
+ */
+ public void setKeyRotationMaxAge(long keyRotationMaxAge) {
+ this.keyRotationMaxAge = keyRotationMaxAge;
+ }
+
+ public long getKeyRotationMaxUsage() {
+ return keyRotationMaxUsage;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys whose recorded usage count
reaches this value. 0 disables usage-based
+ * rotation.
+ */
+ public void setKeyRotationMaxUsage(long keyRotationMaxUsage) {
+ this.keyRotationMaxUsage = keyRotationMaxUsage;
+ }
+
+ /**
+ * The automated key rotation scheduler created when
keyRotationSchedulerEnabled is true, or null.
+ */
+ public KeyRotationScheduler getKeyRotationScheduler() {
+ return keyRotationScheduler;
+ }
}
diff --git
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyRotationScheduler.java
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyRotationScheduler.java
new file mode 100644
index 000000000000..d936576a43b3
--- /dev/null
+++
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyRotationScheduler.java
@@ -0,0 +1,360 @@
+/*
+ * 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.pqc.lifecycle;
+
+import java.security.KeyPair;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An opt-in background scheduler that periodically evaluates the keys managed
by a {@link KeyLifecycleManager} and
+ * rotates the ones that need rotation according to a configurable policy
(maximum age and/or maximum usage). A rotated
+ * key is replaced by a freshly generated key while the previous key is
deprecated - this is the behaviour of
+ * {@link KeyLifecycleManager#rotateKey(String, String, String)}.
+ * <p/>
+ * The scheduler is inactive until {@link #start()} is called. It implements
the Camel {@code Service} contract, so it
+ * can be added to a {@code CamelContext} to participate in the context
lifecycle:
+ *
+ * <pre>{@code
+ * KeyRotationScheduler scheduler = new KeyRotationScheduler(keyManager)
+ * .setCheckInterval(Duration.ofHours(6))
+ * .setMaxKeyAge(Duration.ofDays(90))
+ * .setMaxKeyUsage(1_000_000);
+ * camelContext.addService(scheduler); // started and stopped with the context
+ * }</pre>
+ *
+ * On every tick the scheduler lists the managed keys, keeps only those
matching the configured {@link #setKeyFilter key
+ * filter} (by default only {@link KeyMetadata.KeyStatus#ACTIVE ACTIVE} keys)
and, for each of them, delegates the
+ * decision to {@link KeyLifecycleManager#needsRotation(String, Duration,
long)}. When a key needs rotation, the id of
+ * its replacement is produced by the configured {@link #setKeyIdStrategy key
id strategy} (by default the previous key
+ * id with a rotation marker and timestamp appended).
+ * <p/>
+ * At least one rotation signal should be configured (a {@link #setMaxKeyAge
maximum age}, a {@link #setMaxKeyUsage
+ * maximum usage}, or a per-key {@code nextRotationAt}/{@code
PENDING_ROTATION} status recorded in the key metadata);
+ * otherwise no key will ever be selected for rotation.
+ * <p/>
+ * A single check can also be triggered on demand with {@link
#checkAndRotate()}, which is what the scheduled task
+ * calls. The method is thread-safe: concurrent invocations (for example a
manual call overlapping a scheduled tick) are
+ * serialized.
+ */
+public class KeyRotationScheduler extends ServiceSupport implements
CamelContextAware {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(KeyRotationScheduler.class);
+
+ private final KeyLifecycleManager keyManager;
+
+ private Duration checkInterval = Duration.ofHours(1);
+ private Duration maxKeyAge;
+ private long maxKeyUsage;
+ private Predicate<KeyMetadata> keyFilter = metadata ->
metadata.getStatus() == KeyMetadata.KeyStatus.ACTIVE;
+ private Function<KeyMetadata, String> keyIdStrategy =
KeyRotationScheduler::defaultRotatedKeyId;
+ private KeyRotationListener listener;
+
+ private final AtomicLong checksPerformed = new AtomicLong();
+ private final AtomicLong rotationsPerformed = new AtomicLong();
+ private final AtomicLong rotationFailures = new AtomicLong();
+ private volatile Instant lastCheckAt;
+
+ private ScheduledExecutorService executorService;
+
+ private CamelContext camelContext;
+
+ public KeyRotationScheduler(KeyLifecycleManager keyManager) {
+ this.keyManager = ObjectHelper.notNull(keyManager, "keyManager");
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ /**
+ * Performs a single rotation pass over the managed keys and returns how
many keys were rotated. This is invoked by
+ * the scheduled task and can also be called manually. Never throws for an
individual key failure: such failures are
+ * counted, logged and reported to the {@link KeyRotationListener
listener}, then the pass continues with the next
+ * key.
+ *
+ * @return the number of keys rotated during this pass
+ */
+ public synchronized int checkAndRotate() {
+ checksPerformed.incrementAndGet();
+ lastCheckAt = Instant.now();
+
+ List<KeyMetadata> keys;
+ try {
+ keys = keyManager.listKeys();
+ } catch (Exception e) {
+ LOG.warn("Failed to list keys during rotation check", e);
+ notifyError(null, e);
+ return 0;
+ }
+
+ int rotated = 0;
+ for (KeyMetadata metadata : keys) {
+ if (metadata == null || !keyFilter.test(metadata)) {
+ continue;
+ }
+ String keyId = metadata.getKeyId();
+ try {
+ if (keyManager.needsRotation(keyId, maxKeyAge, maxKeyUsage)) {
+ String newKeyId = keyIdStrategy.apply(metadata);
+ LOG.info("Rotating PQC key '{}' -> '{}' (algorithm={})",
keyId, newKeyId, metadata.getAlgorithm());
+ KeyPair newKeyPair = keyManager.rotateKey(keyId, newKeyId,
metadata.getAlgorithm());
+ rotationsPerformed.incrementAndGet();
+ rotated++;
+ notifyRotated(keyId, newKeyId, metadata, newKeyPair);
+ }
+ } catch (Exception e) {
+ rotationFailures.incrementAndGet();
+ LOG.warn("Failed to rotate PQC key '{}'", keyId, e);
+ notifyError(keyId, e);
+ }
+ }
+ return rotated;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ ObjectHelper.notNull(checkInterval, "checkInterval");
+ if (checkInterval.isNegative() || checkInterval.isZero()) {
+ throw new IllegalArgumentException("checkInterval must be a
positive duration");
+ }
+ long millis = checkInterval.toMillis();
+ // Prefer Camel's ExecutorServiceManager (centralized lifecycle,
profiling, naming) when running inside a
+ // CamelContext; fall back to a self-managed daemon executor for
standalone use.
+ if (camelContext != null) {
+ executorService = camelContext.getExecutorServiceManager()
+ .newSingleThreadScheduledExecutor(this,
"PQCKeyRotationScheduler");
+ } else {
+ executorService = Executors.newSingleThreadScheduledExecutor(new
RotationThreadFactory());
+ }
+ executorService.scheduleWithFixedDelay(this::runScheduledCheck,
millis, millis, TimeUnit.MILLISECONDS);
+ LOG.info("Started PQC KeyRotationScheduler (interval={}, maxAge={},
maxUsage={})",
+ checkInterval, maxKeyAge, maxKeyUsage);
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ if (executorService != null) {
+ if (camelContext != null) {
+
camelContext.getExecutorServiceManager().shutdownNow(executorService);
+ } else {
+ executorService.shutdownNow();
+ }
+ executorService = null;
+ }
+ LOG.info("Stopped PQC KeyRotationScheduler (checks={}, rotations={},
failures={})",
+ checksPerformed.get(), rotationsPerformed.get(),
rotationFailures.get());
+ }
+
+ private void runScheduledCheck() {
+ try {
+ checkAndRotate();
+ } catch (Throwable t) {
+ // Swallow everything so a single failing pass does not cancel the
recurring schedule
+ LOG.warn("Unexpected error during scheduled PQC key rotation
check", t);
+ }
+ }
+
+ private void notifyRotated(String oldKeyId, String newKeyId, KeyMetadata
previousMetadata, KeyPair newKeyPair) {
+ if (listener != null) {
+ try {
+ listener.onRotated(oldKeyId, newKeyId, previousMetadata,
newKeyPair);
+ } catch (Exception e) {
+ LOG.warn("KeyRotationListener.onRotated threw an exception",
e);
+ }
+ }
+ }
+
+ private void notifyError(String keyId, Exception error) {
+ if (listener != null) {
+ try {
+ listener.onError(keyId, error);
+ } catch (Exception e) {
+ LOG.warn("KeyRotationListener.onError threw an exception", e);
+ }
+ }
+ }
+
+ private static String defaultRotatedKeyId(KeyMetadata metadata) {
+ return metadata.getKeyId() + "-rotated-" +
Instant.now().toEpochMilli();
+ }
+
+ public KeyLifecycleManager getKeyManager() {
+ return keyManager;
+ }
+
+ public Duration getCheckInterval() {
+ return checkInterval;
+ }
+
+ /**
+ * How often the managed keys are evaluated for rotation. Must be a
positive duration. Defaults to one hour.
+ */
+ public KeyRotationScheduler setCheckInterval(Duration checkInterval) {
+ this.checkInterval = ObjectHelper.notNull(checkInterval,
"checkInterval");
+ return this;
+ }
+
+ public Duration getMaxKeyAge() {
+ return maxKeyAge;
+ }
+
+ /**
+ * Rotate a key once it is older than this age. When {@code null} (the
default) age is not used as a rotation
+ * signal.
+ */
+ public KeyRotationScheduler setMaxKeyAge(Duration maxKeyAge) {
+ this.maxKeyAge = maxKeyAge;
+ return this;
+ }
+
+ public long getMaxKeyUsage() {
+ return maxKeyUsage;
+ }
+
+ /**
+ * Rotate a key once its recorded usage count reaches this value. When
{@code 0} or negative (the default) usage is
+ * not used as a rotation signal.
+ */
+ public KeyRotationScheduler setMaxKeyUsage(long maxKeyUsage) {
+ this.maxKeyUsage = maxKeyUsage;
+ return this;
+ }
+
+ public Predicate<KeyMetadata> getKeyFilter() {
+ return keyFilter;
+ }
+
+ /**
+ * Selects which keys are considered for rotation. Defaults to only {@link
KeyMetadata.KeyStatus#ACTIVE ACTIVE}
+ * keys.
+ */
+ public KeyRotationScheduler setKeyFilter(Predicate<KeyMetadata> keyFilter)
{
+ this.keyFilter = ObjectHelper.notNull(keyFilter, "keyFilter");
+ return this;
+ }
+
+ public Function<KeyMetadata, String> getKeyIdStrategy() {
+ return keyIdStrategy;
+ }
+
+ /**
+ * Produces the id of the replacement key from the metadata of the key
being rotated. Defaults to the previous key
+ * id with a rotation marker and timestamp appended.
+ */
+ public KeyRotationScheduler setKeyIdStrategy(Function<KeyMetadata, String>
keyIdStrategy) {
+ this.keyIdStrategy = ObjectHelper.notNull(keyIdStrategy,
"keyIdStrategy");
+ return this;
+ }
+
+ public KeyRotationListener getListener() {
+ return listener;
+ }
+
+ /**
+ * An optional listener notified on every rotation and on every rotation
error.
+ */
+ public KeyRotationScheduler setListener(KeyRotationListener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ /**
+ * The number of rotation passes performed so far (scheduled and manual).
+ */
+ public long getChecksPerformed() {
+ return checksPerformed.get();
+ }
+
+ /**
+ * The number of keys successfully rotated so far.
+ */
+ public long getRotationsPerformed() {
+ return rotationsPerformed.get();
+ }
+
+ /**
+ * The number of key rotation attempts that failed so far.
+ */
+ public long getRotationFailures() {
+ return rotationFailures.get();
+ }
+
+ /**
+ * The instant of the last rotation pass, or {@code null} if no pass has
run yet.
+ */
+ public Instant getLastCheckAt() {
+ return lastCheckAt;
+ }
+
+ /**
+ * Listener callbacks invoked by a {@link KeyRotationScheduler}. All
methods have an empty default implementation,
+ * so an implementation only needs to override the callbacks it cares
about.
+ */
+ public interface KeyRotationListener {
+
+ /**
+ * Invoked after a key has been rotated.
+ *
+ * @param oldKeyId the id of the key that was rotated (and is
now deprecated)
+ * @param newKeyId the id of the freshly generated replacement
key
+ * @param previousMetadata the metadata of the rotated key at the time
of rotation
+ * @param newKeyPair the freshly generated key pair
+ */
+ default void onRotated(String oldKeyId, String newKeyId, KeyMetadata
previousMetadata, KeyPair newKeyPair) {
+ }
+
+ /**
+ * Invoked when a rotation attempt fails.
+ *
+ * @param keyId the id of the key that could not be rotated, or {@code
null} when listing the keys failed
+ * @param error the failure
+ */
+ default void onError(String keyId, Exception error) {
+ }
+ }
+
+ private static final class RotationThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, "PQCKeyRotationScheduler");
+ thread.setDaemon(true);
+ return thread;
+ }
+ }
+}
diff --git
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/lifecycle/KeyRotationSchedulerTest.java
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/lifecycle/KeyRotationSchedulerTest.java
new file mode 100644
index 000000000000..35fb53d7d4e2
--- /dev/null
+++
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/lifecycle/KeyRotationSchedulerTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.pqc.lifecycle;
+
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.Security;
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.camel.component.pqc.PQCComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class KeyRotationSchedulerTest {
+
+ @TempDir
+ Path tempDir;
+
+ private FileBasedKeyLifecycleManager keyManager;
+ private KeyRotationScheduler scheduler;
+
+ @BeforeAll
+ static void startup() {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) ==
null) {
+ Security.addProvider(new BouncyCastlePQCProvider());
+ }
+ }
+
+ @AfterEach
+ void cleanup() throws Exception {
+ if (scheduler != null && scheduler.isStarted()) {
+ scheduler.stop();
+ }
+ if (keyManager != null) {
+ for (KeyMetadata metadata : keyManager.listKeys()) {
+ keyManager.deleteKey(metadata.getKeyId());
+ }
+ }
+ }
+
+ private FileBasedKeyLifecycleManager newManager() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+ return keyManager;
+ }
+
+ /** Generates a Dilithium key and drives its recorded usage count to the
given value. */
+ private void generateKeyWithUsage(String keyId, long usage) throws
Exception {
+ keyManager.generateKeyPair("DILITHIUM", keyId,
DilithiumParameterSpec.dilithium2);
+ KeyMetadata metadata = keyManager.getKeyMetadata(keyId);
+ for (long i = 0; i < usage; i++) {
+ metadata.updateLastUsed();
+ }
+ keyManager.updateKeyMetadata(keyId, metadata);
+ }
+
+ @Test
+ void testRotatesKeyThatExceedsUsagePolicy() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ scheduler = new KeyRotationScheduler(keyManager).setMaxKeyUsage(50);
+ int rotated = scheduler.checkAndRotate();
+
+ assertEquals(1, rotated);
+ assertEquals(1, scheduler.getChecksPerformed());
+ assertEquals(1, scheduler.getRotationsPerformed());
+ assertEquals(0, scheduler.getRotationFailures());
+ assertNotNull(scheduler.getLastCheckAt());
+
+ // The old key is deprecated and a single fresh ACTIVE replacement
exists
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED,
keyManager.getKeyMetadata("app-key").getStatus());
+ List<KeyMetadata> active = keyManager.listKeys().stream()
+ .filter(m -> m.getStatus() == KeyMetadata.KeyStatus.ACTIVE)
+ .toList();
+ assertEquals(1, active.size());
+ assertNotEquals("app-key", active.get(0).getKeyId());
+ assertEquals("DILITHIUM", active.get(0).getAlgorithm());
+ }
+
+ @Test
+ void testDoesNotRotateWhenPolicyNotBreached() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 10);
+
+ scheduler = new KeyRotationScheduler(keyManager).setMaxKeyUsage(1000);
+ int rotated = scheduler.checkAndRotate();
+
+ assertEquals(0, rotated);
+ assertEquals(0, scheduler.getRotationsPerformed());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE,
keyManager.getKeyMetadata("app-key").getStatus());
+ assertEquals(1, keyManager.listKeys().size());
+ }
+
+ @Test
+ void testKeyFilterExcludesNonActiveKeys() throws Exception {
+ newManager();
+ // Would breach the usage policy, but the key is not ACTIVE so the
default filter skips it
+ generateKeyWithUsage("app-key", 100);
+ KeyMetadata metadata = keyManager.getKeyMetadata("app-key");
+ metadata.setStatus(KeyMetadata.KeyStatus.DEPRECATED);
+ keyManager.updateKeyMetadata("app-key", metadata);
+
+ scheduler = new KeyRotationScheduler(keyManager).setMaxKeyUsage(50);
+ int rotated = scheduler.checkAndRotate();
+
+ assertEquals(0, rotated);
+ assertEquals(0, scheduler.getRotationsPerformed());
+ assertEquals(1, keyManager.listKeys().size());
+ }
+
+ @Test
+ void testCustomKeyIdStrategy() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setMaxKeyUsage(50)
+ .setKeyIdStrategy(m -> m.getKeyId() + "-v2");
+ scheduler.checkAndRotate();
+
+ KeyMetadata rotated = keyManager.getKeyMetadata("app-key-v2");
+ assertNotNull(rotated);
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, rotated.getStatus());
+ }
+
+ @Test
+ void testListenerInvokedOnRotation() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ AtomicReference<String> oldId = new AtomicReference<>();
+ AtomicReference<String> newId = new AtomicReference<>();
+ AtomicReference<KeyPair> newPair = new AtomicReference<>();
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setMaxKeyUsage(50)
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(
+ String oldKeyId, String newKeyId, KeyMetadata
previousMetadata, KeyPair newKeyPair) {
+ oldId.set(oldKeyId);
+ newId.set(newKeyId);
+ newPair.set(newKeyPair);
+ }
+ });
+ scheduler.checkAndRotate();
+
+ assertEquals("app-key", oldId.get());
+ assertNotNull(newId.get());
+ assertNotEquals("app-key", newId.get());
+ assertNotNull(newPair.get());
+ assertNotNull(newPair.get().getPrivate());
+ }
+
+ @Test
+ void testScheduledRotation() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ CountDownLatch rotated = new CountDownLatch(1);
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setCheckInterval(Duration.ofMillis(50))
+ .setMaxKeyUsage(50)
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(
+ String oldKeyId, String newKeyId, KeyMetadata
previousMetadata, KeyPair newKeyPair) {
+ rotated.countDown();
+ }
+ });
+
+ scheduler.start();
+ assertTrue(rotated.await(10, TimeUnit.SECONDS), "Scheduled rotation
did not run in time");
+ scheduler.stop();
+
+ assertFalse(scheduler.isStarted());
+ assertTrue(scheduler.getRotationsPerformed() >= 1);
+ assertTrue(scheduler.getChecksPerformed() >= 1);
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED,
keyManager.getKeyMetadata("app-key").getStatus());
+ }
+
+ @Test
+ void testStartRejectsNonPositiveInterval() throws Exception {
+ newManager();
+ scheduler = new
KeyRotationScheduler(keyManager).setCheckInterval(Duration.ZERO);
+ assertThrows(RuntimeException.class, () -> scheduler.start());
+ }
+
+ @Test
+ void testScheduledRotationWithCamelContext() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ CountDownLatch rotated = new CountDownLatch(1);
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+ // With a CamelContext set, the scheduler obtains its executor
from the ExecutorServiceManager
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setCheckInterval(Duration.ofMillis(50))
+ .setMaxKeyUsage(50)
+ .setListener(new
KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(
+ String oldKeyId, String newKeyId, KeyMetadata
previousMetadata, KeyPair newKeyPair) {
+ rotated.countDown();
+ }
+ });
+ scheduler.setCamelContext(context);
+ scheduler.start();
+
+ assertTrue(rotated.await(10, TimeUnit.SECONDS), "Scheduled
rotation did not run in time");
+ assertSame(context, scheduler.getCamelContext());
+ scheduler.stop();
+ }
+
+ assertTrue(scheduler.getRotationsPerformed() >= 1);
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED,
keyManager.getKeyMetadata("app-key").getStatus());
+ }
+
+ @Test
+ void testComponentOptionsStartScheduler() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+ PQCComponent component = new PQCComponent();
+ component.setCamelContext(context);
+ component.getConfiguration().setKeyLifecycleManager(keyManager);
+ component.setKeyRotationSchedulerEnabled(true);
+ component.setKeyRotationCheckInterval(3_600_000L); // 1h - won't
auto-fire during the test
+ component.setKeyRotationMaxUsage(50);
+ component.start();
+
+ KeyRotationScheduler s = component.getKeyRotationScheduler();
+ assertNotNull(s);
+ assertSame(context, s.getCamelContext());
+ assertTrue(s.isStarted());
+
+ // Deterministic manual trigger: the component wired the manager
and policy correctly
+ assertEquals(1, s.checkAndRotate());
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED,
keyManager.getKeyMetadata("app-key").getStatus());
+
+ component.stop();
+ assertFalse(s.isStarted());
+ assertNull(component.getKeyRotationScheduler());
+ }
+ }
+}
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
index 6d9dbb4f6003..be73ea8fdd64 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
@@ -307,6 +307,75 @@ public interface PqcComponentBuilderFactory {
return this;
}
+
+ /**
+ * Interval between key rotation checks when the scheduler is enabled.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 3600000
+ * Group: advanced
+ *
+ * @param keyRotationCheckInterval the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationCheckInterval(long
keyRotationCheckInterval) {
+ doSetProperty("keyRotationCheckInterval",
keyRotationCheckInterval);
+ return this;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys older than this age. If
+ * not set, age is not used as a rotation signal.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Group: advanced
+ *
+ * @param keyRotationMaxAge the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationMaxAge(long keyRotationMaxAge) {
+ doSetProperty("keyRotationMaxAge", keyRotationMaxAge);
+ return this;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys whose recorded usage
count
+ * reaches this value. 0 disables usage-based rotation.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Group: advanced
+ *
+ * @param keyRotationMaxUsage the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationMaxUsage(long
keyRotationMaxUsage) {
+ doSetProperty("keyRotationMaxUsage", keyRotationMaxUsage);
+ return this;
+ }
+
+
+ /**
+ * Whether to start an automated background key rotation scheduler for
+ * this component. Requires keyLifecycleManager to be set. The
scheduler
+ * periodically rotates keys that exceed the configured age and/or
usage
+ * policy.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: advanced
+ *
+ * @param keyRotationSchedulerEnabled the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationSchedulerEnabled(boolean
keyRotationSchedulerEnabled) {
+ doSetProperty("keyRotationSchedulerEnabled",
keyRotationSchedulerEnabled);
+ return this;
+ }
+
/**
* A KeyStore where we could get Cryptographic material.
*
@@ -538,6 +607,10 @@ public interface PqcComponentBuilderFactory {
case "keyLifecycleManager":
getOrCreateConfiguration((PQCComponent)
component).setKeyLifecycleManager((org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager)
value); return true;
case "keyPair": getOrCreateConfiguration((PQCComponent)
component).setKeyPair((java.security.KeyPair) value); return true;
case "keyPairAlias": getOrCreateConfiguration((PQCComponent)
component).setKeyPairAlias((java.lang.String) value); return true;
+ case "keyRotationCheckInterval": ((PQCComponent)
component).setKeyRotationCheckInterval((long) value); return true;
+ case "keyRotationMaxAge": ((PQCComponent)
component).setKeyRotationMaxAge((long) value); return true;
+ case "keyRotationMaxUsage": ((PQCComponent)
component).setKeyRotationMaxUsage((long) value); return true;
+ case "keyRotationSchedulerEnabled": ((PQCComponent)
component).setKeyRotationSchedulerEnabled((boolean) value); return true;
case "keyStore": getOrCreateConfiguration((PQCComponent)
component).setKeyStore((java.security.KeyStore) value); return true;
case "keyStorePassword": getOrCreateConfiguration((PQCComponent)
component).setKeyStorePassword((java.lang.String) value); return true;
case "signatureAlgorithm": getOrCreateConfiguration((PQCComponent)
component).setSignatureAlgorithm((java.lang.String) value); return true;