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..88d973b8386e --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurer.java @@ -0,0 +1,62 @@ +// +// 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.springframework.beans.factory.config.PropertyPlaceholderConfigurer; + +/** + * Spring bean that resolves property placeholders, decrypting any value wrapped as + * {@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. 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 = ")"; + + @Override + protected String convertPropertyValue(String originalValue) { + if (originalValue == null || !EncryptionSecretKeyChecker.useEncryption()) { + return originalValue; + } + + String trimmedValue = originalValue.trim(); + if (trimmedValue.startsWith(ENC_PREFIX) && trimmedValue.endsWith(ENC_SUFFIX)) { + 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 new file mode 100644 index 000000000000..60a7e5db0f94 --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/crypt/EncryptablePropertyPlaceholderConfigurerTest.java @@ -0,0 +1,78 @@ +// +// 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.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 convertPropertyValueDecryptsWrappedValueWhenEncryptionEnabled() { + EncryptionSecretKeyChecker.initEncryptor("managementkey"); + + String result = configurer.convertPropertyValue(ENCRYPTED_VALUE); + + Assert.assertEquals("encthis", result); + } + + @Test + public void convertPropertyValueTrimsSurroundingWhitespaceBeforeDecrypting() { + EncryptionSecretKeyChecker.initEncryptor("managementkey"); + + String result = configurer.convertPropertyValue(" " + ENCRYPTED_VALUE + " "); + + Assert.assertEquals("encthis", result); + } + + @Test + public void convertPropertyValuePassesThroughWrappedValueWhenEncryptionNotConfigured() { + String result = configurer.convertPropertyValue(ENCRYPTED_VALUE); + + Assert.assertEquals(ENCRYPTED_VALUE, result); + } + + @Test + public void convertPropertyValuePassesThroughPlainValues() { + EncryptionSecretKeyChecker.initEncryptor("managementkey"); + + String result = configurer.convertPropertyValue("guest"); + + Assert.assertEquals("guest", result); + } + + @Test + public void convertPropertyValueHandlesNull() { + EncryptionSecretKeyChecker.initEncryptor("managementkey"); + + Assert.assertNull(configurer.convertPropertyValue(null)); + } +}