From 55fb559f87e02e42cc34eb64b99fdd7e2a80b50d Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Thu, 23 Jul 2026 10:20:05 +0200 Subject: [PATCH 1/2] add rabbitmq jasypt property encryption facility --- ...ryptablePropertyPlaceholderConfigurer.java | 59 +++++++++++++++ ...ablePropertyPlaceholderConfigurerTest.java | 73 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java create mode 100644 utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java diff --git a/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java b/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java new file mode 100644 index 000000000000..6317019ba033 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java @@ -0,0 +1,59 @@ +// +// 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 com.cloud.utils.crypt; + +import org.jasypt.encryption.StringEncryptor; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; + +/** + * Spring bean that resolves property placeholders, decrypting any value wrapped as + * {@code ENC(...)} with the given jasypt {@link StringEncryptor}. Values that are not wrapped + * are passed through unchanged. + * + * This replaces {@code org.jasypt.spring3.properties.EncryptablePropertyPlaceholderConfigurer} + * (from the jasypt-spring3 artifact), which is not on the classpath and is incompatible with + * Spring 5, so beans referencing it fail with a ClassNotFoundException. + */ +public class EncryptablePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { + + private static final String ENC_PREFIX = "ENC("; + private static final String ENC_SUFFIX = ")"; + + private final StringEncryptor encryptor; + + public EncryptablePropertyPlaceholderConfigurer(StringEncryptor encryptor) { + this.encryptor = encryptor; + } + + @Override + protected String convertPropertyValue(String originalValue) { + if (originalValue == null) { + return null; + } + + String trimmedValue = originalValue.trim(); + if (trimmedValue.startsWith(ENC_PREFIX) && trimmedValue.endsWith(ENC_SUFFIX)) { + String encryptedValue = trimmedValue.substring(ENC_PREFIX.length(), trimmedValue.length() - ENC_SUFFIX.length()); + return encryptor.decrypt(encryptedValue); + } + + return originalValue; + } +} diff --git a/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java b/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java new file mode 100644 index 000000000000..1ab2cb2b74a1 --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java @@ -0,0 +1,73 @@ +// +// 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 com.cloud.utils.crypt; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import org.jasypt.encryption.StringEncryptor; +import org.junit.Assert; +import org.junit.Test; + +public class EncryptablePropertyPlaceholderConfigurerTest { + + @Test + public void convertPropertyValueDecryptsWrappedValue() { + StringEncryptor encryptor = mock(StringEncryptor.class); + when(encryptor.decrypt("bmb2VaFdQb")).thenReturn("secret"); + EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + + String result = configurer.convertPropertyValue("ENC(bmb2VaFdQb)"); + + Assert.assertEquals("secret", result); + } + + @Test + public void convertPropertyValueTrimsSurroundingWhitespaceBeforeDecrypting() { + StringEncryptor encryptor = mock(StringEncryptor.class); + when(encryptor.decrypt("bmb2VaFdQb")).thenReturn("secret"); + EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + + String result = configurer.convertPropertyValue(" ENC(bmb2VaFdQb) "); + + Assert.assertEquals("secret", result); + } + + @Test + public void convertPropertyValuePassesThroughPlainValues() { + StringEncryptor encryptor = mock(StringEncryptor.class); + EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + + String result = configurer.convertPropertyValue("guest"); + + Assert.assertEquals("guest", result); + verifyNoInteractions(encryptor); + } + + @Test + public void convertPropertyValueHandlesNull() { + StringEncryptor encryptor = mock(StringEncryptor.class); + EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + + Assert.assertNull(configurer.convertPropertyValue(null)); + verifyNoInteractions(encryptor); + } +} From 1e9623abf7bc4e6b01d854bfc5efe241abf87a33 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Fri, 21 Aug 2026 10:47:49 +0200 Subject: [PATCH 2/2] replace jasypt string encryptor by ACS generic code --- ...ryptablePropertyPlaceholderConfigurer.java | 31 ++++++------ ...ablePropertyPlaceholderConfigurerTest.java | 49 ++++++++++--------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java b/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java index 6317019ba033..88d973b8386e 100644 --- a/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java +++ b/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java @@ -19,39 +19,42 @@ package com.cloud.utils.crypt; -import org.jasypt.encryption.StringEncryptor; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; /** * Spring bean that resolves property placeholders, decrypting any value wrapped as - * {@code ENC(...)} with the given jasypt {@link StringEncryptor}. Values that are not wrapped - * are passed through unchanged. + * {@code ENC(...)} with the management server's own secret key, via + * {@link EncryptionSecretKeyChecker} (the same AES-GCM based mechanism already used to + * encrypt {@code db.properties}). Values that are not wrapped are passed through unchanged, + * and if encryption has not been configured on the management server, decryption is skipped + * entirely and the raw (still-wrapped) value is returned. * * This replaces {@code org.jasypt.spring3.properties.EncryptablePropertyPlaceholderConfigurer} * (from the jasypt-spring3 artifact), which is not on the classpath and is incompatible with - * Spring 5, so beans referencing it fail with a ClassNotFoundException. + * Spring 5, so beans referencing it fail with a ClassNotFoundException. Unlike that class, this + * one needs no separate {@code StringEncryptor}/algorithm bean wired in: declare it as + *
{@code
+ * 
+ *     
+ * 
+ * }
+ * and it reuses whichever secret key the management server was configured with (file/env/web, + * see {@code password.encryption.type} in db.properties). */ public class EncryptablePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { private static final String ENC_PREFIX = "ENC("; private static final String ENC_SUFFIX = ")"; - private final StringEncryptor encryptor; - - public EncryptablePropertyPlaceholderConfigurer(StringEncryptor encryptor) { - this.encryptor = encryptor; - } - @Override protected String convertPropertyValue(String originalValue) { - if (originalValue == null) { - return null; + if (originalValue == null || !EncryptionSecretKeyChecker.useEncryption()) { + return originalValue; } String trimmedValue = originalValue.trim(); if (trimmedValue.startsWith(ENC_PREFIX) && trimmedValue.endsWith(ENC_SUFFIX)) { - String encryptedValue = trimmedValue.substring(ENC_PREFIX.length(), trimmedValue.length() - ENC_SUFFIX.length()); - return encryptor.decrypt(encryptedValue); + return EncryptionSecretKeyChecker.decryptPropertyIfNeeded(trimmedValue); } return originalValue; diff --git a/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java b/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java index 1ab2cb2b74a1..60a7e5db0f94 100644 --- a/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java +++ b/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java @@ -19,55 +19,60 @@ package com.cloud.utils.crypt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -import org.jasypt.encryption.StringEncryptor; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; public class EncryptablePropertyPlaceholderConfigurerTest { + private static final String ENCRYPTED_VALUE = "ENC(iYVsCZXiGiC6SzZLMNBvBL93hoUpntxkuRjyaqC8L+JYKXw=)"; + + private final EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(); + + @After + public void tearDown() { + EncryptionSecretKeyChecker.resetEncryptor(); + } + @Test - public void convertPropertyValueDecryptsWrappedValue() { - StringEncryptor encryptor = mock(StringEncryptor.class); - when(encryptor.decrypt("bmb2VaFdQb")).thenReturn("secret"); - EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + public void convertPropertyValueDecryptsWrappedValueWhenEncryptionEnabled() { + EncryptionSecretKeyChecker.initEncryptor("managementkey"); - String result = configurer.convertPropertyValue("ENC(bmb2VaFdQb)"); + String result = configurer.convertPropertyValue(ENCRYPTED_VALUE); - Assert.assertEquals("secret", result); + Assert.assertEquals("encthis", result); } @Test public void convertPropertyValueTrimsSurroundingWhitespaceBeforeDecrypting() { - StringEncryptor encryptor = mock(StringEncryptor.class); - when(encryptor.decrypt("bmb2VaFdQb")).thenReturn("secret"); - EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + EncryptionSecretKeyChecker.initEncryptor("managementkey"); - String result = configurer.convertPropertyValue(" ENC(bmb2VaFdQb) "); + String result = configurer.convertPropertyValue(" " + ENCRYPTED_VALUE + " "); + + Assert.assertEquals("encthis", result); + } + + @Test + public void convertPropertyValuePassesThroughWrappedValueWhenEncryptionNotConfigured() { + String result = configurer.convertPropertyValue(ENCRYPTED_VALUE); - Assert.assertEquals("secret", result); + Assert.assertEquals(ENCRYPTED_VALUE, result); } @Test public void convertPropertyValuePassesThroughPlainValues() { - StringEncryptor encryptor = mock(StringEncryptor.class); - EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + EncryptionSecretKeyChecker.initEncryptor("managementkey"); String result = configurer.convertPropertyValue("guest"); Assert.assertEquals("guest", result); - verifyNoInteractions(encryptor); } @Test public void convertPropertyValueHandlesNull() { - StringEncryptor encryptor = mock(StringEncryptor.class); - EncryptablePropertyPlaceholderConfigurer configurer = new EncryptablePropertyPlaceholderConfigurer(encryptor); + EncryptionSecretKeyChecker.initEncryptor("managementkey"); Assert.assertNull(configurer.convertPropertyValue(null)); - verifyNoInteractions(encryptor); } }