Introduce TextEncryptorLocator abstraction

Allows more customization of encryption and decryption stack. Should
be no change for existing users.

Fixes gh-124
This commit is contained in:
wojtkiewicz
2015-04-10 15:05:12 +02:00
committed by Dave Syer
parent ce3a20d262
commit 7437cdef49
17 changed files with 495 additions and 130 deletions

View File

@@ -1,15 +1,30 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.client;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.config.client.ConfigClientAutoConfiguration;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertEquals;
public class ConfigClientAutoConfigurationTests {
@Test

View File

@@ -17,9 +17,9 @@ package org.springframework.cloud.config.server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.util.StringUtils;
/**
@@ -29,19 +29,18 @@ import org.springframework.util.StringUtils;
@Configuration
@ConditionalOnWebApplication
public class ConfigServerMvcConfiguration {
@Autowired(required = false)
private TextEncryptor encryptor;
@Autowired
private EnvironmentRepository repository;
@Autowired
private ConfigServerProperties server;
@Autowired
private EnvironmentEncryptor environmentEncryptor;
@Bean
public EnvironmentController environmentController() {
EnvironmentController controller = new EnvironmentController(repository, encryptionController());
EnvironmentController controller = new EnvironmentController(repository, environmentEncryptor);
controller.setDefaultLabel(getDefaultLabel());
controller.setOverrides(server.getOverrides());
controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml());
@@ -56,13 +55,4 @@ public class ConfigServerMvcConfiguration {
return repository.getDefaultLabel();
}
}
@Bean
public EncryptionController encryptionController() {
EncryptionController controller = new EncryptionController();
if (encryptor!=null) {
controller.setEncryptor(encryptor);
}
return controller;
}
}

View File

@@ -57,6 +57,17 @@ public class ConfigServerProperties {
*/
private boolean stripDocumentFromYaml = true;
/**
* Default application name when incoming requests do not have a specific one.
*/
private String defaultApplicationName = "application";
/**
* Default application profile when incoming requests do not have a specific one.
*/
private String defaultProfile = "default";
public String getDefaultLabel() {
return defaultLabel;
}
@@ -97,4 +108,19 @@ public class ConfigServerProperties {
this.stripDocumentFromYaml = stripDocumentFromYaml;
}
public String getDefaultApplicationName() {
return defaultApplicationName;
}
public void setDefaultApplicationName(String defaultApplicationName) {
this.defaultApplicationName = defaultApplicationName;
}
public String getDefaultProfile() {
return defaultProfile;
}
public void setDefaultProfile(String defaultProfile) {
this.defaultProfile = defaultProfile;
}
}

View File

@@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.config.server.encryption.ConfigServerEncryptionConfiguration;
import org.springframework.context.annotation.Import;
/**
@@ -30,7 +31,9 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ConfigServerConfiguration.class, ConfigServerMvcConfiguration.class})
@Import({ConfigServerConfiguration.class,
ConfigServerMvcConfiguration.class,
ConfigServerEncryptionConfiguration.class})
public @interface EnableConfigServer {
}

View File

@@ -21,16 +21,13 @@ import java.net.URLDecoder;
import java.security.KeyPair;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.cloud.context.encrypt.KeyFormatException;
import org.springframework.core.io.ByteArrayResource;
@@ -44,6 +41,7 @@ import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.rsa.crypto.RsaKeyHolder;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -63,15 +61,19 @@ public class EncryptionController {
private static Log logger = LogFactory.getLog(EncryptionController.class);
private TextEncryptor encryptor;
private final TextEncryptorLocator encryptorLocator;
@Autowired(required = false)
public void setEncryptor(TextEncryptor encryptor) {
this.encryptor = encryptor;
private final ConfigServerProperties properties;
public EncryptionController(TextEncryptorLocator encryptorLocator,
ConfigServerProperties configServerProperties) {
this.encryptorLocator = encryptorLocator;
this.properties = configServerProperties;
}
@RequestMapping(value = "/key", method = RequestMethod.GET)
public String getPublicKey() {
TextEncryptor encryptor = locateDefaultTextEncryptor();
if (!(encryptor instanceof RsaKeyHolder)) {
throw new KeyNotAvailableException();
}
@@ -90,8 +92,9 @@ public class EncryptionController {
ByteArrayResource resource = new ByteArrayResource(file.getBytes());
KeyPair keyPair = new KeyStoreKeyFactory(resource, password.toCharArray())
.getKeyPair(alias);
encryptor = new RsaSecretEncryptor(keyPair);
body.put("publicKey", ((RsaKeyHolder) encryptor).getPublicKey());
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(keyPair);
updateEncryptor(encryptor);
body.put("publicKey", encryptor.getPublicKey());
}
catch (IOException e) {
throw new KeyFormatException();
@@ -102,21 +105,31 @@ public class EncryptionController {
}
@RequestMapping(value = "/key", method = RequestMethod.POST, params = { "!password" })
@RequestMapping(value = "/key", method = RequestMethod.POST, params = { "!password" })
public ResponseEntity<Map<String, Object>> uploadKey(@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
Map<String, Object> body = new HashMap<String, Object>();
body.put("status", "OK");
encryptor = new EncryptorFactory().create(stripFormData(data, type, false));
TextEncryptor encryptor = new EncryptorFactory().create(stripFormData(data, type, false));
updateEncryptor(encryptor);
if (encryptor instanceof RsaKeyHolder) {
body.put("publicKey", ((RsaKeyHolder) encryptor).getPublicKey());
}
logger.info("Key changed with literal value");
return new ResponseEntity<Map<String, Object>>(body, HttpStatus.CREATED);
}
// this is temporary solution to support existing REST API
// downcasting is only necessary until we introduce some key management abstraction,
// we don't want TextEncryptorLocator to have setEncryptor method
private void updateEncryptor(TextEncryptor encryptor) {
if (encryptorLocator instanceof SingleTextEncryptorLocator) {
((SingleTextEncryptorLocator) encryptorLocator).setEncryptor(encryptor);
} else {
throw new IncompatibleTextEncryptorLocatorException();
}
}
@ExceptionHandler(KeyFormatException.class)
@@ -139,39 +152,72 @@ public class EncryptionController {
@RequestMapping(value = "encrypt/status", method = RequestMethod.GET)
public Map<String, Object> status() {
if (encryptor == null) {
throw new KeyNotInstalledException();
}
checkEncryptorInstalled(locateDefaultTextEncryptor());
return Collections.<String, Object> singletonMap("status", "OK");
}
@RequestMapping(value = "encrypt", method = RequestMethod.POST)
public String encrypt(@RequestBody String data,
public String encrypt(
@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
if (encryptor == null) {
throw new KeyNotInstalledException();
return encrypt(properties.getDefaultApplicationName(),
properties.getDefaultProfile(), data, type);
}
@RequestMapping(value = "/encrypt/{name}/{profiles}", method = RequestMethod.POST)
public String encrypt(
@PathVariable String name,
@PathVariable String profiles,
@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
try {
TextEncryptor encryptor = checkEncryptorInstalled(encryptorLocator.locate(name, profiles));
String encrypted = encryptor.encrypt(stripFormData(data, type, false));
logger.info("Encrypted data");
return encrypted;
} catch (IllegalArgumentException e) {
throw new InvalidCipherException();
}
data = stripFormData(data, type, false);
String encrypted = encryptor.encrypt(data);
logger.info("Encrypted data");
return encrypted;
}
@RequestMapping(value = "decrypt", method = RequestMethod.POST)
public String decrypt(@RequestBody String data,
public String decrypt(
@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
return decrypt(properties.getDefaultApplicationName(),
properties.getDefaultProfile(), data, type);
}
@RequestMapping(value = "/decrypt/{name}/{profiles}", method = RequestMethod.POST)
public String decrypt(
@PathVariable String name,
@PathVariable String profiles,
@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
try {
TextEncryptor encryptor = checkEncryptorInstalled(encryptorLocator.locate(name, profiles));
String decrypted = encryptor.decrypt(stripFormData(data, type, true));
logger.info("Decrypted cipher data");
return decrypted;
} catch (IllegalArgumentException e) {
throw new InvalidCipherException();
}
}
private TextEncryptor checkEncryptorInstalled(TextEncryptor encryptor) {
if (encryptor == null) {
throw new KeyNotInstalledException();
}
try {
data = stripFormData(data, type, true);
String decrypted = encryptor.decrypt(data);
logger.info("Decrypted cipher data");
return decrypted;
}
catch (IllegalArgumentException e) {
throw new InvalidCipherException();
}
return encryptor;
}
private TextEncryptor locateDefaultTextEncryptor() {
return encryptorLocator.locate(properties.getDefaultApplicationName(),
properties.getDefaultProfile());
}
private String stripFormData(String data, MediaType type, boolean cipher) {
@@ -228,40 +274,6 @@ public class EncryptionController {
return new ResponseEntity<Map<String, Object>>(body, HttpStatus.BAD_REQUEST);
}
public Environment decrypt(Environment environment) {
Environment result = new Environment(environment.getName(), environment.getProfiles(),
environment.getLabel());
for (PropertySource source : environment.getPropertySources()) {
Map<Object, Object> map = new LinkedHashMap<Object, Object>(
source.getSource());
for (Entry<Object,Object> entry : new LinkedHashSet<>(map.entrySet())) {
Object key = entry.getKey();
String name = key.toString();
String value = entry.getValue().toString();
if (value.startsWith("{cipher}")) {
map.remove(key);
if (encryptor == null) {
map.put(name, value);
}
else {
try {
value = value == null ? null : encryptor.decrypt(value
.substring("{cipher}".length()));
}
catch (Exception e) {
value = "<n/a>";
name = "invalid." + name;
logger.warn("Cannot decrypt key: " + key + " ("
+ e.getClass() + ": " + e.getMessage() + ")");
}
map.put(name, value);
}
}
}
result.add(new PropertySource(source.getName(), map));
}
return result;
}
}
@SuppressWarnings("serial")
@@ -275,3 +287,7 @@ class KeyNotAvailableException extends RuntimeException {
@SuppressWarnings("serial")
class InvalidCipherException extends RuntimeException {
}
@SuppressWarnings("serial")
class IncompatibleTextEncryptorLocatorException extends RuntimeException {
}

View File

@@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.http.HttpHeaders;
@@ -51,6 +52,9 @@ import org.yaml.snakeyaml.nodes.Tag;
* @author Dave Syer
* @author Spencer Gibb
* @author Roy Clarkson
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
@RestController
@RequestMapping("${spring.cloud.config.server.prefix:}")
@@ -60,7 +64,7 @@ public class EnvironmentController {
private EnvironmentRepository repository;
private EncryptionController encryption;
private EnvironmentEncryptor environmentEncryptor;
private String defaultLabel;
@@ -68,12 +72,11 @@ public class EnvironmentController {
private boolean stripDocument = true;
public EnvironmentController(EnvironmentRepository repository,
EncryptionController encryption) {
public EnvironmentController(EnvironmentRepository repository, EnvironmentEncryptor environmentEncryptor) {
super();
this.repository = repository;
this.defaultLabel = repository.getDefaultLabel();
this.encryption = encryption;
this.environmentEncryptor = environmentEncryptor;
}
/**
@@ -94,7 +97,7 @@ public class EnvironmentController {
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
Environment environment = encryption.decrypt(repository.findOne(name, profiles,
Environment environment = environmentEncryptor.decrypt(repository.findOne(name, profiles,
label));
if (!overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", overrides));

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.server.encryption;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* EnvironmentEncryptor that can decrypt property values prefixed with {cipher} marker.
*
* @author Dave Syer
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
@Component
public class CipherEnvironmentEncryptor implements EnvironmentEncryptor {
private static Log logger = LogFactory.getLog(CipherEnvironmentEncryptor.class);
private final TextEncryptorLocator encryptorLocator;
@Autowired
public CipherEnvironmentEncryptor(TextEncryptorLocator encryptorLocator) {
this.encryptorLocator = encryptorLocator;
}
@Override
public Environment decrypt(Environment environment) {
TextEncryptor encryptor = encryptorLocator.locate(environment.getName(),
StringUtils.arrayToCommaDelimitedString(environment.getProfiles()));
return encryptor != null ? decrypt(environment, encryptor) : environment;
}
private Environment decrypt(Environment environment, TextEncryptor encryptor) {
Environment result = new Environment(environment.getName(),
environment.getProfiles(), environment.getLabel());
for (PropertySource source : environment.getPropertySources()) {
Map<Object, Object> map = new LinkedHashMap<Object, Object>(source.getSource());
for (Map.Entry<Object, Object> entry : new LinkedHashSet<>(map.entrySet())) {
Object key = entry.getKey();
String name = key.toString();
String value = entry.getValue().toString();
if (value.startsWith("{cipher}")) {
map.remove(key);
try {
value = value == null ? null : encryptor.decrypt(value.substring(
"{cipher}".length()));
} catch (Exception e) {
value = "<n/a>";
name = "invalid." + name;
logger.warn("Cannot decrypt key: " + key
+ " (" + e.getClass() + ": " + e.getMessage() + ")");
}
map.put(name, value);
}
}
result.add(new PropertySource(source.getName(), map));
}
return result;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.server.encryption;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.config.server.ConfigServerProperties;
import org.springframework.cloud.config.server.EncryptionController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
@Configuration
public class ConfigServerEncryptionConfiguration {
@Autowired(required = false)
private TextEncryptor encryptor;
@Autowired
private TextEncryptorLocator locator;
@Autowired
private ConfigServerProperties properties;
@Bean
public EncryptionController encryptionController() {
return new EncryptionController(locator, properties);
}
@Bean
public EnvironmentEncryptor environmentEncryptor() {
return new CipherEnvironmentEncryptor(locator);
}
@Bean
@ConditionalOnMissingBean
public TextEncryptorLocator textEncryptorLocator() {
return new SingleTextEncryptorLocator(encryptor);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.server.encryption;
import org.springframework.cloud.config.environment.Environment;
/**
* Service interface for decrypting properties in Environment object.
*
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
public interface EnvironmentEncryptor {
Environment decrypt(Environment environment);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.server.encryption;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* Basic implementation of TextEncryptorLocator which uses
* single TextEncryptor for all applications and profiles.
*
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
public class SingleTextEncryptorLocator implements TextEncryptorLocator {
private TextEncryptor encryptor;
public SingleTextEncryptorLocator() {
}
public SingleTextEncryptorLocator(TextEncryptor encryptor) {
this.encryptor = encryptor;
}
// temporary solution to support EncryptionController REST API
public void setEncryptor(TextEncryptor encryptor) {
this.encryptor = encryptor;
}
@Override
public TextEncryptor locate(String applicationName, String profiles) {
return encryptor;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.server.encryption;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* Service interface for locating proper TextEncryptor to be used for particular application.
* It can be used to provide config server with application and environment specific encryption.
*
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
public interface TextEncryptorLocator {
/**
* Returns TextEncryptor to be used for given application and profiles.
*
* @param applicationName application name
* @param profiles comma separated list of profiles
*/
TextEncryptor locate(String applicationName, String profiles);
}

View File

@@ -1,14 +1,12 @@
package org.springframework.cloud.config.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -26,6 +24,11 @@ import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyString;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@IntegrationTest("server.port:0")
@@ -63,7 +66,9 @@ public class ConfigClientOffIntegrationTests {
@Bean
public EnvironmentRepository environmentRepository() {
return Mockito.mock(EnvironmentRepository.class);
EnvironmentRepository repository = Mockito.mock(EnvironmentRepository.class);
given(repository.findOne(anyString(), anyString(), anyString())).willReturn(new Environment("", ""));
return repository;
}
}

View File

@@ -1,14 +1,12 @@
package org.springframework.cloud.config.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -26,6 +24,11 @@ import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyString;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@IntegrationTest({ "server.port:0", "spring.cloud.config.enabled:true" })
@@ -63,7 +66,9 @@ public class ConfigClientOnIntegrationTests {
@Bean
public EnvironmentRepository environmentRepository() {
return Mockito.mock(EnvironmentRepository.class);
EnvironmentRepository repository = Mockito.mock(EnvironmentRepository.class);
given(repository.findOne(anyString(), anyString(), anyString())).willReturn(new Environment("", ""));
return repository;
}
}

View File

@@ -15,29 +15,26 @@
*/
package org.springframework.cloud.config.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import org.junit.Test;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.EncryptionController;
import org.springframework.cloud.config.server.InvalidCipherException;
import org.springframework.cloud.config.server.KeyNotInstalledException;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.cloud.context.encrypt.KeyFormatException;
import org.springframework.http.MediaType;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Dave Syer
*
*/
public class EncryptionControllerTests {
private EncryptionController controller = new EncryptionController();
private SingleTextEncryptorLocator textEncryptorLocator = new SingleTextEncryptorLocator();
private ConfigServerProperties properties = new ConfigServerProperties();
private EncryptionController controller = new EncryptionController(textEncryptorLocator, properties);
@Test(expected = KeyNotInstalledException.class)
public void cannotDecryptWithoutKey() {
@@ -69,38 +66,27 @@ public class EncryptionControllerTests {
@Test
public void sunnyDayRsaKey() {
controller.setEncryptor(new RsaSecretEncryptor());
textEncryptorLocator.setEncryptor(new RsaSecretEncryptor());
String cipher = controller.encrypt("foo", MediaType.TEXT_PLAIN);
assertEquals("foo", controller.decrypt(cipher, MediaType.TEXT_PLAIN));
}
@Test
public void publicKey() {
controller.setEncryptor(new RsaSecretEncryptor());
textEncryptorLocator.setEncryptor(new RsaSecretEncryptor());
String key = controller.getPublicKey();
assertTrue("Wrong key format: " + key, key.startsWith("ssh-rsa"));
}
@Test
public void formDataIn() {
controller.setEncryptor(new RsaSecretEncryptor());
textEncryptorLocator.setEncryptor(new RsaSecretEncryptor());
// Add space to input
String cipher = controller.encrypt("foo bar=", MediaType.APPLICATION_FORM_URLENCODED);
String decrypt = controller.decrypt(cipher + "=", MediaType.APPLICATION_FORM_URLENCODED);
assertEquals("Wrong decrypted plaintext: " + decrypt, "foo bar", decrypt);
}
@Test
public void decryptEnvironment() {
controller.uploadKey("foo", MediaType.TEXT_PLAIN);
String cipher = controller.encrypt("foo", MediaType.TEXT_PLAIN);
Environment environment = new Environment("foo", "bar");
environment.add(new PropertySource("spam", Collections
.<Object, Object> singletonMap("my", "{cipher}" + cipher)));
Environment result = controller.decrypt(environment);
assertEquals("foo", result.getPropertySources().get(0).getSource().get("my"));
}
@Test
public void randomizedCipher() {
controller.uploadKey("foo", MediaType.TEXT_PLAIN);

View File

@@ -19,7 +19,10 @@ package org.springframework.cloud.config.server;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.encryption.CipherEnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@@ -38,7 +41,7 @@ public class EnvironmentControllerIntegrationTests {
public void init() {
Mockito.when(repository.getDefaultLabel()).thenReturn("master");
mvc = MockMvcBuilders.standaloneSetup(
new EnvironmentController(repository, new EncryptionController()))
new EnvironmentController(repository, new CipherEnvironmentEncryptor(new SingleTextEncryptorLocator())))
.build();
}

View File

@@ -28,6 +28,8 @@ import org.mockito.Mockito;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.encryption.CipherEnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -55,7 +57,7 @@ public class EnvironmentControllerTests {
@Before
public void init() {
Mockito.when(repository.getDefaultLabel()).thenReturn("master");
this.controller = new EnvironmentController(repository, new EncryptionController());
this.controller = new EnvironmentController(repository, new CipherEnvironmentEncryptor(new SingleTextEncryptorLocator()));
}
@Test

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2015 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
*
* 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.springframework.cloud.config.server.encryption;
import java.util.Collections;
import org.junit.Test;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import static java.util.UUID.randomUUID;
import static org.junit.Assert.assertEquals;
public class CipherEnvironmentEncryptorTests {
TextEncryptor textEncryptor = new EncryptorFactory().create("foo");
TextEncryptorLocator textEncryptorLocator = new SingleTextEncryptorLocator(textEncryptor);
EnvironmentEncryptor encryptor = new CipherEnvironmentEncryptor(textEncryptorLocator);
@Test
public void shouldDecryptEnvironment() {
// given
String secret = randomUUID().toString();
// when
Environment environment = new Environment("name", "profile", "label");
environment.add(new PropertySource("a",
Collections.<Object, Object>singletonMap(environment.getName(), "{cipher}" + textEncryptor.encrypt(secret))));
// then
assertEquals(secret, encryptor.decrypt(environment).getPropertySources().get(0).getSource().get(environment.getName()));
}
}