AbstractEnvironmentDecryptTests.java
/*
* Copyright 2013-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.bootstrap.encrypt;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.DECRYPTED_PROPERTY_SOURCE_NAME;
@ExtendWith(OutputCaptureExtension.class)
public class AbstractEnvironmentDecryptTests {
private final AbstractEnvironmentDecrypt decryptor = new AbstractEnvironmentDecrypt() {
};
private ConfigurableEnvironment environment;
@BeforeEach
void setup() {
environment = new AnnotationConfigApplicationContext().getEnvironment();
}
@Test
void decryptCipherKey() {
environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar")));
decrypt();
then(environment.getProperty("foo")).isEqualTo("bar");
}
@Test
void decryptCipherKeyWithPriority() {
environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar")));
environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("foo", "{cipher}spam")));
decrypt();
then(environment.getProperty("foo")).isEqualTo("spam");
}
@Test
void relaxedBinding() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1",
Map.of("foo.text", "{cipher}foo1", "bar_text", "bar1", "baz[0].text", "baz1")));
environment.getPropertySources()
.addFirst(new MapPropertySource("source-2",
Map.of("FOO_TEXT", "{cipher}foo2", "BAR_TEXT", "{cipher}bar2", "BAZ[0].TEXT", "{cipher}baz2")));
decrypt();
then(environment.getProperty("foo.text")).isEqualTo("foo2");
then(environment.getProperty("bar-text")).isEqualTo("bar2");
then(environment.getProperty("baz[0].text")).isEqualTo("baz2");
}
@Test
void errorOnDecrypt(CapturedOutput output) {
environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar")));
assertThatThrownBy(() -> decrypt(Encryptors.text("deadbeef", "AFFE37")))
.isInstanceOf(IllegalStateException.class);
// Assert logs contain warning even when exception thrown
then(output.toString()).contains("Cannot decrypt: key=foo");
}
@Test
void errorOnDecryptWhenFailOnErrorIsOff(CapturedOutput output) {
environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar")));
decryptor.setFailOnError(false);
decrypt(Encryptors.text("deadbeef", "AFFE37"));
// Assert logs contain warning
then(output.toString()).contains("Cannot decrypt: key=foo");
// Empty is the safest fallback for undecryptable cipher
then(environment.getProperty("foo")).isEqualTo("");
}
@Test
void indexedPropertiesAreCopied() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1",
Map.of("yours[0].someValue", "yourFoo", "yours[1].someValue", "yourBar")));
// collection with some encrypted keys and some not encrypted
environment.getPropertySources()
.addFirst(new MapPropertySource("source-2",
Map.of("mine[0].someValue", "Foo", "mine[0].someKey", "{cipher}Foo0", "mine[1].someValue", "Bar",
"mine[1].someKey", "{cipher}Bar1", "nonindexed", "nonindexval")));
decrypt();
then(environment.getProperty("mine[0].someValue")).isEqualTo("Foo");
then(environment.getProperty("mine[0].someKey")).isEqualTo("Foo0");
then(environment.getProperty("mine[1].someValue")).isEqualTo("Bar");
then(environment.getProperty("mine[1].someKey")).isEqualTo("Bar1");
then(environment.getProperty("yours[0].someValue")).isEqualTo("yourFoo");
then(environment.getProperty("yours[1].someValue")).isEqualTo("yourBar");
}
@Test
void indexedPropertiesAreCopiedOnlyIfEncrypted() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1",
Map.of("a[0]", "a0", "a[1]", "{cipher}a1", "b[0]", "b0", "b[1]", "b1")));
environment.getPropertySources()
.addFirst(new MapPropertySource("source-2", Map.of("b[0]", "updated-b0", "b[1]", "updated-b1")));
decrypt();
then(environment.getProperty("a[0]")).isEqualTo("a0");
then(environment.getProperty("a[1]")).isEqualTo("a1");
then(environment.getProperty("b[0]")).isEqualTo("updated-b0");
then(environment.getProperty("b[1]")).isEqualTo("updated-b1");
var decryptedPropertySource = environment.getPropertySources().get("decrypted");
then(decryptedPropertySource).isNotNull();
var source = decryptedPropertySource.getSource();
then(source).isInstanceOf(Map.class);
then(((Map<?, ?>) source).size()).as("decrypted property source had wrong size").isEqualTo(2);
}
@Test
void decryptCompositePropertySource() {
var cps = new CompositePropertySource("composite-source");
cps.addPropertySource(new MapPropertySource("dev-profile", Map.of("key", "{cipher}value1")));
cps.addPropertySource(new MapPropertySource("default-profile", Map.of("key", "{cipher}value2")));
environment.getPropertySources().addFirst(cps);
decrypt();
then(environment.getProperty("key")).isEqualTo("value1");
}
@Test
void propertySourcesOrderedCorrectlyWithUnencryptedOverrides() {
environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar")));
environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("foo", "spam")));
decrypt();
then(environment.getProperty("foo")).isEqualTo("spam");
}
@Test
void decryptOnlyIfNotOverridden() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar", "foo2", "{cipher}bar2")));
environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("foo", "spam")));
decrypt();
then(environment.getProperty("foo2")).isEqualTo("bar2");
then(environment.getProperty("foo")).isEqualTo("spam");
}
@Test
void indexedPropertiesAreHandledCorrectly() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1",
Map.of("list[0].plain", "good", "list[0].cipher", "{cipher}bad")));
environment.getPropertySources()
.addFirst(new MapPropertySource("source-2", Map.of("list[0].plain", "well", "list[0].cipher", "worse")));
decrypt();
then(environment.getProperty("list[0].plain")).isEqualTo("well");
then(environment.getProperty("list[0].cipher")).isEqualTo("worse");
}
@Test
void anonymousIndexedPropertiesAreHandledCorrectly() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1", Map.of("[0].plain", "good", "[0].cipher", "{cipher}bad")));
environment.getPropertySources()
.addFirst(new MapPropertySource("source-2",
Map.of("[0].plain", "well", "[0].cipher", "{cipher}worse", "another[1].text", "old")));
environment.getPropertySources()
.addFirst(new MapPropertySource("source-3", Map.of("another[1].text", "updated")));
decrypt();
then(environment.getProperty("[0].plain")).isEqualTo("well");
then(environment.getProperty("[0].cipher")).isEqualTo("worse");
then(environment.getProperty("another[1].text")).isEqualTo("updated");
}
@Test
void indexedPropertiesWithSimilarNamesAreHandledCorrectly() {
environment.getPropertySources()
.addFirst(new MapPropertySource("source-1",
Map.of("foo[0]", "plain", "foo[1]", "{cipher}cipher", "fooBar[0]", "old")));
environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("fooBar[0]", "updated")));
decrypt();
then(environment.getProperty("foo[0]")).isEqualTo("plain");
then(environment.getProperty("foo[1]")).isEqualTo("cipher");
then(environment.getProperty("fooBar[0]")).isEqualTo("updated");
}
private void decrypt() {
decrypt(Encryptors.noOpText());
}
private void decrypt(TextEncryptor encryptor) {
var decrypted = decryptor.decrypt(encryptor, environment.getPropertySources());
environment.getPropertySources()
.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, decrypted));
}
}