This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch swift-school in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
commit 27c4054b365c99bab49d2e2f1a6e3b002a5f3938 Author: Guillaume Nodet <[email protected]> AuthorDate: Sat Apr 25 21:16:04 2026 +0200 CAMEL-23250: Add Spring Boot auto-configuration for security policy properties Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../CamelSecurityPolicyAutoConfiguration.java | 151 +++++++++++++++++++ ...CamelSecurityPolicyConfigurationProperties.java | 119 +++++++++++++++ ...rk.boot.autoconfigure.AutoConfiguration.imports | 1 + .../CamelSecurityPolicyAutoConfigurationTest.java | 163 +++++++++++++++++++++ 4 files changed, 434 insertions(+) diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyAutoConfiguration.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyAutoConfiguration.java new file mode 100644 index 00000000000..c0c18a84a9f --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyAutoConfiguration.java @@ -0,0 +1,151 @@ +/* + * 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.spring.boot.security; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.CamelContext; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.main.SecurityConfigurationProperties; +import org.apache.camel.main.SecurityPolicyResult; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.support.CamelContextHelper; +import org.apache.camel.util.SecurityUtils; +import org.apache.camel.util.SecurityViolation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; + +@AutoConfiguration(after = CamelAutoConfiguration.class) +@ConditionalOnBean(CamelAutoConfiguration.class) +@EnableConfigurationProperties(CamelSecurityPolicyConfigurationProperties.class) +public class CamelSecurityPolicyAutoConfiguration { + + private static final Logger LOG = LoggerFactory.getLogger(CamelSecurityPolicyAutoConfiguration.class); + + @Bean + SecurityPolicyResult camelSecurityPolicyResult( + CamelContext camelContext, + CamelSecurityPolicyConfigurationProperties config, + Environment environment) { + + SecurityConfigurationProperties securityConfig = applySecurityProperties(camelContext, config); + + Map<String, Object> camelProperties = extractCamelProperties(environment); + + List<SecurityViolation> violations = SecurityUtils.detectViolations( + camelProperties, + (k, v) -> containsSensitive(camelContext, k, v), + securityConfig::resolvePolicy, + securityConfig.getAllowedPropertySet()); + + SecurityPolicyResult result = new SecurityPolicyResult(violations); + camelContext.getCamelContextExtension().addContextPlugin(SecurityPolicyResult.class, result); + + enforceViolations(violations); + + return result; + } + + private SecurityConfigurationProperties applySecurityProperties( + CamelContext camelContext, + CamelSecurityPolicyConfigurationProperties config) { + + // get the security config from camel-main's MainConfigurationProperties + // which is already bound by CamelAutoConfiguration via CamelConfigurationProperties + SecurityConfigurationProperties securityConfig + = new SecurityConfigurationProperties(null); + + securityConfig.setPolicy(config.getPolicy()); + if (config.getSecretPolicy() != null) { + securityConfig.setSecretPolicy(config.getSecretPolicy()); + } + if (config.getInsecureSslPolicy() != null) { + securityConfig.setInsecureSslPolicy(config.getInsecureSslPolicy()); + } + if (config.getInsecureSerializationPolicy() != null) { + securityConfig.setInsecureSerializationPolicy(config.getInsecureSerializationPolicy()); + } + if (config.getInsecureDevPolicy() != null) { + securityConfig.setInsecureDevPolicy(config.getInsecureDevPolicy()); + } + if (config.getAllowedProperties() != null) { + securityConfig.setAllowedProperties(config.getAllowedProperties()); + } + + return securityConfig; + } + + private static Map<String, Object> extractCamelProperties(Environment environment) { + Map<String, Object> properties = new LinkedHashMap<>(); + + if (environment instanceof ConfigurableEnvironment ce) { + ce.getPropertySources().forEach(ps -> { + if (ps instanceof EnumerablePropertySource<?> eps) { + for (String name : eps.getPropertyNames()) { + if (name.startsWith("camel.") && !name.startsWith("camel.security.")) { + Object value = environment.getProperty(name); + if (value != null) { + properties.putIfAbsent(name, value); + } + } + } + } + }); + } + + return properties; + } + + private static boolean containsSensitive(CamelContext camelContext, String key, Object value) { + boolean answer = CamelContextHelper.containsSensitive(camelContext, key); + if (!answer && value != null) { + String v = value.toString(); + answer = v.startsWith("RAW("); + } + return answer; + } + + private static void enforceViolations(List<SecurityViolation> violations) { + List<String> failures = new ArrayList<>(); + for (SecurityViolation v : violations) { + if ("fail".equals(v.policy())) { + failures.add(v.toString()); + } else { + LOG.warn("SECURITY WARNING: {}", v); + } + } + + if (!failures.isEmpty()) { + throw new RuntimeCamelException( + "Security policy violations detected (policy=fail):\n - " + String.join("\n - ", failures) + + "\nTo allow specific properties, add them to camel.security.allowed-properties" + + " or change the policy to 'warn' or 'allow'."); + } + } + +} diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyConfigurationProperties.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyConfigurationProperties.java new file mode 100644 index 00000000000..d358776e600 --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyConfigurationProperties.java @@ -0,0 +1,119 @@ +/* + * 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.spring.boot.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Security policy configuration for Camel Spring Boot applications. + * <p> + * Controls how Camel reacts to insecure configuration at startup. Policies can be set globally or per security + * category: + * <ul> + * <li>{@code allow} — no warnings, allow the configuration</li> + * <li>{@code warn} — log a warning at startup (default)</li> + * <li>{@code fail} — throw an exception and prevent startup</li> + * </ul> + */ +@ConfigurationProperties(prefix = "camel.security") +public class CamelSecurityPolicyConfigurationProperties { + + /** + * Global security policy applied to all categories unless overridden. Controls how Camel reacts when insecure + * configuration is detected at startup. + */ + private String policy = "warn"; + + /** + * Security policy for plain-text secrets. When set, overrides the global policy for properties that contain + * sensitive values configured as plain text. + */ + private String secretPolicy; + + /** + * Security policy for insecure SSL/TLS configuration. When set, overrides the global policy for options that + * disable certificate validation or hostname verification. + */ + private String insecureSslPolicy; + + /** + * Security policy for insecure deserialization configuration. When set, overrides the global policy for options that + * enable dangerous deserialization of untrusted data. + */ + private String insecureSerializationPolicy; + + /** + * Security policy for development-only features. When set, overrides the global policy for options intended only for + * development environments. + */ + private String insecureDevPolicy; + + /** + * Comma-separated list of property keys to exclude from security policy checks. Use full property paths (e.g., + * camel.component.aws2-s3.trustAllCertificates) to allow specific properties regardless of the configured policy. + */ + private String allowedProperties; + + public String getPolicy() { + return policy; + } + + public void setPolicy(String policy) { + this.policy = policy; + } + + public String getSecretPolicy() { + return secretPolicy; + } + + public void setSecretPolicy(String secretPolicy) { + this.secretPolicy = secretPolicy; + } + + public String getInsecureSslPolicy() { + return insecureSslPolicy; + } + + public void setInsecureSslPolicy(String insecureSslPolicy) { + this.insecureSslPolicy = insecureSslPolicy; + } + + public String getInsecureSerializationPolicy() { + return insecureSerializationPolicy; + } + + public void setInsecureSerializationPolicy(String insecureSerializationPolicy) { + this.insecureSerializationPolicy = insecureSerializationPolicy; + } + + public String getInsecureDevPolicy() { + return insecureDevPolicy; + } + + public void setInsecureDevPolicy(String insecureDevPolicy) { + this.insecureDevPolicy = insecureDevPolicy; + } + + public String getAllowedProperties() { + return allowedProperties; + } + + public void setAllowedProperties(String allowedProperties) { + this.allowedProperties = allowedProperties; + } + +} diff --git a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index e2dd68b6d89..31f55659dc8 100644 --- a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -25,6 +25,7 @@ org.apache.camel.spring.boot.actuate.health.CamelAvailabilityCheckAutoConfigurat org.apache.camel.spring.boot.actuate.info.CamelInfoAutoConfiguration org.apache.camel.spring.boot.cluster.ClusteredRouteControllerAutoConfiguration org.apache.camel.spring.boot.routecontroller.SupervisingRouteControllerAutoConfiguration +org.apache.camel.spring.boot.security.CamelSecurityPolicyAutoConfiguration org.apache.camel.spring.boot.security.CamelSSLAutoConfiguration org.apache.camel.spring.boot.threadpool.CamelThreadPoolAutoConfiguration org.apache.camel.spring.boot.trace.CamelTraceAutoConfiguration diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyAutoConfigurationTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyAutoConfigurationTest.java new file mode 100644 index 00000000000..31d1585b95b --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/security/CamelSecurityPolicyAutoConfigurationTest.java @@ -0,0 +1,163 @@ +/* + * 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.spring.boot.security; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.main.SecurityPolicyResult; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public class CamelSecurityPolicyAutoConfigurationTest { + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelSecurityPolicyAutoConfiguration.class)); + + @Test + public void noSecurityPropertiesShouldStartNormally() { + runner.run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result).isNotNull(); + assertThat(result.hasViolations()).isFalse(); + }); + } + + @Test + public void policyAllowShouldIgnoreInsecureConfig() { + runner.withPropertyValues( + "camel.security.policy=allow", + "camel.component.http.trustAllCertificates=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.hasViolations()).isFalse(); + }); + } + + @Test + public void policyWarnShouldStartWithViolations() { + runner.withPropertyValues( + "camel.security.policy=warn", + "camel.component.http.trustAllCertificates=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.hasViolations()).isTrue(); + assertThat(result.getViolations()).anyMatch( + v -> v.propertyKey().contains("trustAllCertificates") && "warn".equals(v.policy())); + }); + } + + @Test + public void policyFailShouldPreventStartup() { + runner.withPropertyValues( + "camel.security.policy=fail", + "camel.component.http.trustAllCertificates=true") + .run(context -> { + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure()) + .rootCause() + .isInstanceOf(RuntimeCamelException.class) + .hasMessageContaining("Security policy violations detected"); + }); + } + + @Test + public void categoryOverrideShouldTakePrecedence() { + runner.withPropertyValues( + "camel.security.policy=fail", + "camel.security.insecure-ssl-policy=allow", + "camel.component.http.trustAllCertificates=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.hasViolations()).isFalse(); + }); + } + + @Test + public void categoryOverrideWarnWhileGlobalFail() { + runner.withPropertyValues( + "camel.security.policy=fail", + "camel.security.insecure-ssl-policy=warn", + "camel.component.http.trustAllCertificates=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.hasViolations()).isTrue(); + assertThat(result.getViolations()).anyMatch( + v -> "warn".equals(v.policy())); + }); + } + + @Test + public void allowedPropertiesShouldExcludeFromChecks() { + runner.withPropertyValues( + "camel.security.policy=fail", + "camel.security.allowed-properties=camel.component.http.trustAllCertificates", + "camel.component.http.trustAllCertificates=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.hasViolations()).isFalse(); + }); + } + + @Test + public void multipleViolationsDetected() { + runner.withPropertyValues( + "camel.security.policy=warn", + "camel.component.http.trustAllCertificates=true", + "camel.component.netty.allowJavaSerializedObject=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.getViolationCount()).isGreaterThanOrEqualTo(2); + }); + } + + @Test + public void insecureDevPolicyOverride() { + runner.withPropertyValues( + "camel.security.policy=fail", + "camel.security.insecure-dev-policy=allow", + "camel.main.devConsoleEnabled=true") + .run(context -> { + assertThat(context).hasNotFailed(); + }); + } + + @Test + public void insecureSerializationPolicyOverride() { + runner.withPropertyValues( + "camel.security.policy=fail", + "camel.security.insecure-serialization-policy=warn", + "camel.component.netty.allowJavaSerializedObject=true") + .run(context -> { + assertThat(context).hasNotFailed(); + SecurityPolicyResult result = context.getBean(SecurityPolicyResult.class); + assertThat(result.hasViolations()).isTrue(); + }); + } + +}
